Merge pull request #477 from aiken-lang/blueprint-json-deserializers
Blueprint schema validation
This commit is contained in:
commit
ee8509956d
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,10 @@
|
|||
use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc};
|
||||
|
||||
use uplc::{ast::Type as UplcType, builtins::DefaultFunction};
|
||||
|
||||
use self::{environment::Environment, pretty::Printer};
|
||||
use crate::{
|
||||
ast::{Constant, DefinitionLocation, ModuleKind, Span},
|
||||
tipo::fields::FieldMap,
|
||||
};
|
||||
|
||||
use self::{environment::Environment, pretty::Printer};
|
||||
use std::{cell::RefCell, collections::HashMap, ops::Deref, sync::Arc};
|
||||
use uplc::{ast::Type as UplcType, builtins::DefaultFunction};
|
||||
|
||||
mod environment;
|
||||
pub mod error;
|
||||
|
|
|
@ -20,6 +20,7 @@ ignore = "0.4.20"
|
|||
indexmap = "1.9.2"
|
||||
itertools = "0.10.5"
|
||||
miette = { version = "5.5.0", features = ["fancy"] }
|
||||
minicbor = "0.19.1"
|
||||
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
|
||||
pallas = "0.18.0"
|
||||
pallas-traverse = "0.18.0"
|
||||
|
@ -37,3 +38,6 @@ toml = "0.7.2"
|
|||
uplc = { path = '../uplc', version = "0.0.29" }
|
||||
walkdir = "2.3.2"
|
||||
zip = "0.6.4"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.1.0"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use aiken_lang::tipo::{Type, TypeVar};
|
||||
use serde::{
|
||||
self,
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
use std::{
|
||||
|
@ -52,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>(
|
||||
|
@ -94,14 +101,14 @@ impl Reference {
|
|||
}
|
||||
|
||||
/// Turn a reference into a key suitable for lookup.
|
||||
fn as_key(&self) -> &str {
|
||||
pub(crate) fn as_key(&self) -> &str {
|
||||
self.inner.as_str()
|
||||
}
|
||||
|
||||
/// Turn a reference into a valid JSON pointer. Note that the JSON pointer specification
|
||||
/// indicates that '/' must be escaped as '~1' in pointer addresses (as they are otherwise
|
||||
/// treated as path delimiter in pointers paths).
|
||||
fn as_json_pointer(&self) -> String {
|
||||
pub(crate) fn as_json_pointer(&self) -> String {
|
||||
format!("#/definitions/{}", self.as_key().replace('/', "~1"))
|
||||
}
|
||||
}
|
||||
|
@ -181,3 +188,55 @@ impl Serialize for Reference {
|
|||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Reference {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(serde::Deserialize)]
|
||||
enum Field {
|
||||
#[serde(rename = "$ref")]
|
||||
Ref,
|
||||
}
|
||||
const FIELDS: &[&str] = &["$ref"];
|
||||
|
||||
struct ReferenceVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> for ReferenceVisitor {
|
||||
type Value = Reference;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Reference")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Reference, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let mut inner = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Ref => {
|
||||
if inner.is_some() {
|
||||
return Err(de::Error::duplicate_field(FIELDS[0]));
|
||||
}
|
||||
inner = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inner: String = inner.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?;
|
||||
|
||||
match inner.strip_prefix("#/definitions/") {
|
||||
Some(suffix) => Ok(Reference {
|
||||
inner: suffix.to_string(),
|
||||
}),
|
||||
None => Err(de::Error::custom(
|
||||
"Invalid reference; only local JSON pointer to #/definitions are allowed.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct("Reference", FIELDS, ReferenceVisitor)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
use super::schema;
|
||||
use super::{
|
||||
definitions::Reference,
|
||||
schema::{self, Schema},
|
||||
};
|
||||
use aiken_lang::ast::Span;
|
||||
use miette::{Diagnostic, NamedSource};
|
||||
use minicbor as cbor;
|
||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||
use std::fmt::Debug;
|
||||
use uplc::ast::Constant;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Diagnostic)]
|
||||
pub enum Error {
|
||||
|
@ -37,9 +42,61 @@ pub enum Error {
|
|||
)]
|
||||
#[diagnostic(code("aiken::blueprint::address::parameterized"))]
|
||||
#[diagnostic(help(
|
||||
"I can only compute addresses of validators that are fully applied. For example, a {keyword_spend} validator must have exactly 3 arguments: a datum, a redeemer and a context. If it has more, they need to be provided beforehand and applied directly in the validator. Applying parameters change the validator's compiled code, and thus the address.\n\nThis is why I need you to apply parameters first.",
|
||||
keyword_spend = "spend".if_supports_color(Stdout, |s| s.purple())))]
|
||||
"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.",
|
||||
keyword_spend = "spend".if_supports_color(Stdout, |s| s.yellow()),
|
||||
spend_arity = "3".if_supports_color(Stdout, |s| s.yellow()),
|
||||
blueprint_apply_command = "blueprint apply".if_supports_color(Stdout, |s| s.purple()),
|
||||
))]
|
||||
ParameterizedValidator { n: usize },
|
||||
|
||||
#[error("I stumble upon something else than a constant when I expected one.")]
|
||||
#[diagnostic(code("aiken:blueprint::apply::malformed::argument"))]
|
||||
#[diagnostic(help(
|
||||
"Parameters applied to blueprints must be constant; they cannot be lambdas or delayed terms."
|
||||
))]
|
||||
NonConstantParameter,
|
||||
|
||||
#[error("I couldn't find a definition corresponding to a reference.")]
|
||||
#[diagnostic(code("aiken::blueprint::apply::unknown::reference"))]
|
||||
#[diagnostic(help(
|
||||
"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().if_supports_color(Stdout, |s| s.red())
|
||||
))]
|
||||
UnresolvedSchemaReference { reference: Reference },
|
||||
|
||||
#[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, 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()),
|
||||
term = {
|
||||
let mut buf = vec![];
|
||||
match term {
|
||||
Constant::Data(data) => {
|
||||
cbor::encode(data, &mut buf).unwrap();
|
||||
cbor::display(&buf).to_string()
|
||||
},
|
||||
_ => term.to_pretty()
|
||||
}
|
||||
}.if_supports_color(Stdout, |s| s.red()),
|
||||
))]
|
||||
SchemaMismatch { schema: Schema, term: Constant },
|
||||
|
||||
#[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 },
|
||||
|
||||
#[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 {}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
pub mod definitions;
|
||||
pub mod error;
|
||||
pub mod parameter;
|
||||
pub mod schema;
|
||||
pub mod validator;
|
||||
|
||||
use crate::{config::Config, module::CheckedModules};
|
||||
use aiken_lang::gen_uplc::CodeGenerator;
|
||||
use definitions::{Definitions, Reference};
|
||||
use definitions::Definitions;
|
||||
use error::Error;
|
||||
use schema::{Annotated, Schema};
|
||||
use std::fmt::Debug;
|
||||
use validator::Validator;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Blueprint<R: Default, S: Default> {
|
||||
pub struct Blueprint {
|
||||
pub preamble: Preamble,
|
||||
pub validators: Vec<Validator<R, S>>,
|
||||
pub validators: Vec<Validator>,
|
||||
#[serde(skip_serializing_if = "Definitions::is_empty", default)]
|
||||
pub definitions: Definitions<S>,
|
||||
pub definitions: Definitions<Annotated<Schema>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -48,7 +49,7 @@ pub enum LookupResult<'a, T> {
|
|||
Many,
|
||||
}
|
||||
|
||||
impl Blueprint<Reference, Annotated<Schema>> {
|
||||
impl Blueprint {
|
||||
pub fn new(
|
||||
config: &Config,
|
||||
modules: &CheckedModules,
|
||||
|
@ -82,12 +83,8 @@ impl Blueprint<Reference, Annotated<Schema>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, S> Blueprint<R, S>
|
||||
where
|
||||
R: Clone + Default,
|
||||
S: Clone + Default,
|
||||
{
|
||||
pub fn lookup(&self, title: Option<&String>) -> Option<LookupResult<Validator<R, S>>> {
|
||||
impl Blueprint {
|
||||
pub fn lookup(&self, title: Option<&String>) -> Option<LookupResult<Validator>> {
|
||||
let mut validator = None;
|
||||
|
||||
for v in self.validators.iter() {
|
||||
|
@ -112,7 +109,7 @@ where
|
|||
action: F,
|
||||
) -> Result<A, E>
|
||||
where
|
||||
F: Fn(Validator<R, S>) -> Result<A, E>,
|
||||
F: Fn(Validator) -> Result<A, E>,
|
||||
{
|
||||
match self.lookup(title) {
|
||||
Some(LookupResult::One(validator)) => action(validator.to_owned()),
|
||||
|
@ -146,13 +143,13 @@ impl From<&Config> for Preamble {
|
|||
mod test {
|
||||
use super::*;
|
||||
use aiken_lang::builtins;
|
||||
use schema::{Data, Items, Schema};
|
||||
use schema::{Data, Declaration, Items, Schema};
|
||||
use serde_json::{self, json};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn serialize_no_description() {
|
||||
let blueprint: Blueprint<Reference, Annotated<Schema>> = Blueprint {
|
||||
let blueprint = Blueprint {
|
||||
preamble: Preamble {
|
||||
title: "Foo".to_string(),
|
||||
description: None,
|
||||
|
@ -179,7 +176,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn serialize_with_description() {
|
||||
let blueprint: Blueprint<Reference, Annotated<Schema>> = Blueprint {
|
||||
let blueprint = Blueprint {
|
||||
preamble: Preamble {
|
||||
title: "Foo".to_string(),
|
||||
description: Some("Lorem ipsum".to_string()),
|
||||
|
@ -222,12 +219,15 @@ mod test {
|
|||
&HashMap::new(),
|
||||
|_| Ok(Schema::Data(Data::Bytes).into()),
|
||||
)?;
|
||||
Ok(Schema::Data(Data::List(Items::One(Box::new(ref_bytes)))).into())
|
||||
Ok(
|
||||
Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_bytes))))
|
||||
.into(),
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let blueprint: Blueprint<Reference, Annotated<Schema>> = Blueprint {
|
||||
let blueprint = Blueprint {
|
||||
preamble: Preamble {
|
||||
title: "Foo".to_string(),
|
||||
description: None,
|
||||
|
|
|
@ -0,0 +1,428 @@
|
|||
use super::{
|
||||
definitions::{Definitions, Reference},
|
||||
error::Error,
|
||||
schema::{Annotated, Constructor, Data, Declaration, Items, Schema},
|
||||
};
|
||||
use std::{iter, ops::Deref};
|
||||
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,
|
||||
}
|
||||
|
||||
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: &Term<DeBruijn>,
|
||||
) -> Result<(), Error> {
|
||||
let schema = &definitions
|
||||
.lookup(&self.schema)
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| {
|
||||
Err(Error::UnresolvedSchemaReference {
|
||||
reference: self.schema.clone(),
|
||||
})
|
||||
})?
|
||||
.annotated;
|
||||
|
||||
if let Term::Constant(constant) = term {
|
||||
validate_schema(schema, definitions, constant)
|
||||
} else {
|
||||
Err(Error::NonConstantParameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mismatch(term: &Constant, schema: Schema) -> Error {
|
||||
Error::SchemaMismatch {
|
||||
schema,
|
||||
term: term.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_schema(
|
||||
schema: &Schema,
|
||||
definitions: &Definitions<Annotated<Schema>>,
|
||||
term: &Constant,
|
||||
) -> 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: &Constant,
|
||||
) -> 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(mismatch(
|
||||
term,
|
||||
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(),
|
||||
)),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_data(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::Data(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Data(Data::Opaque)))
|
||||
}
|
||||
|
||||
fn expect_data_integer(term: &Constant) -> Result<(), Error> {
|
||||
if let Constant::Data(data) = term {
|
||||
if matches!(data, PlutusData::BigInt(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Data(Data::Integer)))
|
||||
}
|
||||
|
||||
fn expect_data_bytes(term: &Constant) -> Result<(), Error> {
|
||||
if let Constant::Data(data) = term {
|
||||
if matches!(data, PlutusData::BoundedBytes(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Data(Data::Bytes)))
|
||||
}
|
||||
|
||||
fn expect_data_list(term: &Constant) -> Result<Vec<Constant>, Error> {
|
||||
if let Constant::Data(PlutusData::Array(elems)) = term {
|
||||
return Ok(elems
|
||||
.iter()
|
||||
.map(|elem| Constant::Data(elem.to_owned()))
|
||||
.collect());
|
||||
}
|
||||
|
||||
Err(mismatch(
|
||||
term,
|
||||
Schema::Data(Data::List(Items::One(Declaration::Inline(Box::new(
|
||||
Data::Opaque,
|
||||
))))),
|
||||
))
|
||||
}
|
||||
|
||||
fn expect_data_map(term: &Constant) -> Result<Vec<(Constant, Constant)>, Error> {
|
||||
if let Constant::Data(PlutusData::Map(pairs)) = term {
|
||||
return Ok(pairs
|
||||
.iter()
|
||||
.map(|(k, v)| (Constant::Data(k.to_owned()), Constant::Data(v.to_owned())))
|
||||
.collect());
|
||||
}
|
||||
|
||||
Err(mismatch(
|
||||
term,
|
||||
Schema::Data(Data::Map(
|
||||
Declaration::Inline(Box::new(Data::Opaque)),
|
||||
Declaration::Inline(Box::new(Data::Opaque)),
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
fn expect_data_constr(term: &Constant, index: usize) -> Result<Vec<Constant>, Error> {
|
||||
if let Constant::Data(PlutusData::Constr(constr)) = term {
|
||||
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| Constant::Data(field.to_owned()))
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(mismatch(
|
||||
term,
|
||||
Schema::Data(Data::AnyOf(vec![Constructor {
|
||||
index,
|
||||
fields: vec![],
|
||||
}
|
||||
.into()])),
|
||||
))
|
||||
}
|
||||
|
||||
fn expect_unit(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::Unit) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Unit))
|
||||
}
|
||||
|
||||
fn expect_integer(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::Integer(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Integer))
|
||||
}
|
||||
|
||||
fn expect_bytes(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::ByteString(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Bytes))
|
||||
}
|
||||
|
||||
fn expect_string(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::String(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::String))
|
||||
}
|
||||
|
||||
fn expect_boolean(term: &Constant) -> Result<(), Error> {
|
||||
if matches!(term, Constant::Bool(..)) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(mismatch(term, Schema::Boolean))
|
||||
}
|
||||
|
||||
fn expect_pair(term: &Constant) -> Result<(Constant, Constant), Error> {
|
||||
if let Constant::ProtoPair(_, _, left, right) = term {
|
||||
return Ok((left.deref().clone(), right.deref().clone()));
|
||||
}
|
||||
|
||||
Err(mismatch(
|
||||
term,
|
||||
Schema::Pair(
|
||||
Declaration::Inline(Box::new(Schema::Data(Data::Opaque))),
|
||||
Declaration::Inline(Box::new(Schema::Data(Data::Opaque))),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn expect_list(term: &Constant) -> Result<Vec<Constant>, Error> {
|
||||
if let Constant::ProtoList(_, elems) = term {
|
||||
return Ok(elems.to_owned());
|
||||
}
|
||||
|
||||
Err(mismatch(
|
||||
term,
|
||||
Schema::List(Items::One(Declaration::Inline(Box::new(Schema::Data(
|
||||
Data::Opaque,
|
||||
))))),
|
||||
))
|
||||
}
|
|
@ -8,10 +8,11 @@ use aiken_lang::{
|
|||
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||
use serde::{
|
||||
self,
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use serde_json as json;
|
||||
use std::{collections::HashMap, fmt, ops::Deref, sync::Arc};
|
||||
|
||||
// NOTE: Can be anything BUT 0
|
||||
pub const REDEEMER_DISCRIMINANT: usize = 1;
|
||||
|
@ -26,6 +27,50 @@ pub struct Annotated<T> {
|
|||
pub annotated: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Declaration<T> {
|
||||
Referenced(Reference),
|
||||
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 {
|
||||
|
@ -34,8 +79,8 @@ pub enum Schema {
|
|||
Integer,
|
||||
Bytes,
|
||||
String,
|
||||
Pair(Data, Data),
|
||||
List(Vec<Data>),
|
||||
Pair(Declaration<Schema>, Declaration<Schema>),
|
||||
List(Items<Schema>),
|
||||
Data(Data),
|
||||
}
|
||||
|
||||
|
@ -44,24 +89,25 @@ pub enum Schema {
|
|||
pub enum Data {
|
||||
Integer,
|
||||
Bytes,
|
||||
List(Items<Reference>),
|
||||
Map(Box<Reference>, Box<Reference>),
|
||||
List(Items<Data>),
|
||||
Map(Declaration<Data>, Declaration<Data>),
|
||||
AnyOf(Vec<Annotated<Constructor>>),
|
||||
Opaque,
|
||||
}
|
||||
|
||||
/// A structure that represents either one or many elements.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Items<T> {
|
||||
One(Box<T>),
|
||||
Many(Vec<T>),
|
||||
One(Declaration<T>),
|
||||
Many(Vec<Declaration<T>>),
|
||||
}
|
||||
|
||||
/// Captures a single UPLC constructor with its
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Constructor {
|
||||
pub index: usize,
|
||||
pub fields: Vec<Annotated<Reference>>,
|
||||
pub fields: Vec<Annotated<Declaration<Data>>>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for Annotated<T> {
|
||||
|
@ -90,7 +136,7 @@ impl Annotated<Schema> {
|
|||
description: Some("A redeemer wrapped in an extra constructor to make multi-validator detection possible on-chain.".to_string()),
|
||||
annotated: Schema::Data(Data::AnyOf(vec![Constructor {
|
||||
index: REDEEMER_DISCRIMINANT,
|
||||
fields: vec![schema.into()],
|
||||
fields: vec![Declaration::Referenced(schema).into()],
|
||||
}
|
||||
.into()])),
|
||||
})
|
||||
|
@ -219,7 +265,7 @@ impl Annotated<Schema> {
|
|||
description: Some("An optional value.".to_string()),
|
||||
annotated: Constructor {
|
||||
index: 0,
|
||||
fields: vec![generic.into()],
|
||||
fields: vec![Declaration::Referenced(generic).into()],
|
||||
},
|
||||
},
|
||||
Annotated {
|
||||
|
@ -260,22 +306,15 @@ impl Annotated<Schema> {
|
|||
} if xs.len() == 2 => {
|
||||
definitions.remove(&generic);
|
||||
Data::Map(
|
||||
Box::new(
|
||||
xs.first()
|
||||
.expect("length (== 2) checked in pattern clause")
|
||||
.to_owned(),
|
||||
),
|
||||
Box::new(
|
||||
xs.last()
|
||||
.expect("length (== 2) checked in pattern clause")
|
||||
.to_owned(),
|
||||
),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
// let inner = schema.clone().into_data(type_info)?.annotated;
|
||||
Data::List(Items::One(Box::new(generic)))
|
||||
}
|
||||
_ => Data::List(Items::One(Declaration::Referenced(generic))),
|
||||
};
|
||||
|
||||
Ok(Schema::Data(data).into())
|
||||
|
@ -320,6 +359,7 @@ impl Annotated<Schema> {
|
|||
.iter()
|
||||
.map(|elem| {
|
||||
Annotated::do_from_type(elem, modules, type_parameters, definitions)
|
||||
.map(Declaration::Referenced)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| e.backtrack(type_info))?;
|
||||
|
@ -398,7 +438,7 @@ impl Data {
|
|||
fields.push(Annotated {
|
||||
title: field.label.clone(),
|
||||
description: field.doc.clone().map(|s| s.trim().to_string()),
|
||||
annotated: reference,
|
||||
annotated: Declaration::Referenced(reference),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -510,6 +550,245 @@ impl Serialize for Schema {
|
|||
}
|
||||
}
|
||||
|
||||
fn visit_schema<'a, V>(mut map: V) -> Result<Schema, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "camelCase")]
|
||||
enum Field {
|
||||
DataType,
|
||||
Items,
|
||||
Keys,
|
||||
Values,
|
||||
Left,
|
||||
Right,
|
||||
AnyOf,
|
||||
OneOf,
|
||||
}
|
||||
|
||||
let mut data_type: Option<String> = None;
|
||||
let mut items: Option<json::Value> = None; // defer items deserialization to later
|
||||
let mut keys = None;
|
||||
let mut left = None;
|
||||
let mut right = None;
|
||||
let mut values = None;
|
||||
let mut any_of = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::DataType => {
|
||||
if data_type.is_some() {
|
||||
return Err(de::Error::duplicate_field("dataType"));
|
||||
}
|
||||
data_type = Some(map.next_value()?);
|
||||
}
|
||||
Field::Items => {
|
||||
if items.is_some() {
|
||||
return Err(de::Error::duplicate_field("items"));
|
||||
}
|
||||
items = Some(map.next_value()?);
|
||||
}
|
||||
Field::Keys => {
|
||||
if keys.is_some() {
|
||||
return Err(de::Error::duplicate_field("keys"));
|
||||
}
|
||||
keys = Some(map.next_value()?);
|
||||
}
|
||||
Field::Values => {
|
||||
if values.is_some() {
|
||||
return Err(de::Error::duplicate_field("values"));
|
||||
}
|
||||
values = Some(map.next_value()?);
|
||||
}
|
||||
Field::Left => {
|
||||
if left.is_some() {
|
||||
return Err(de::Error::duplicate_field("left"));
|
||||
}
|
||||
left = Some(map.next_value()?);
|
||||
}
|
||||
Field::Right => {
|
||||
if right.is_some() {
|
||||
return Err(de::Error::duplicate_field("right"));
|
||||
}
|
||||
right = Some(map.next_value()?);
|
||||
}
|
||||
Field::AnyOf => {
|
||||
if any_of.is_some() {
|
||||
return Err(de::Error::duplicate_field("anyOf/oneOf"));
|
||||
}
|
||||
any_of = Some(map.next_value()?);
|
||||
}
|
||||
Field::OneOf => {
|
||||
if any_of.is_some() {
|
||||
return Err(de::Error::duplicate_field("anyOf/oneOf"));
|
||||
}
|
||||
any_of = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let expect_data_items = || match &items {
|
||||
Some(items) => serde_json::from_value::<Items<Data>>(items.clone())
|
||||
.map_err(|e| de::Error::custom(e.to_string())),
|
||||
None => Err(de::Error::missing_field("items")),
|
||||
};
|
||||
|
||||
let expect_schema_items = || match &items {
|
||||
Some(items) => serde_json::from_value::<Items<Schema>>(items.clone())
|
||||
.map_err(|e| de::Error::custom(e.to_string())),
|
||||
None => Err(de::Error::missing_field("items")),
|
||||
};
|
||||
|
||||
let expect_no_items = || {
|
||||
if items.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"unexpected fields 'items' for non-list data-type",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let expect_no_keys = || {
|
||||
if keys.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"unexpected fields 'keys' for non-map data-type",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let expect_no_values = || {
|
||||
if values.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"unexpected fields 'values' for non-map data-type",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let expect_no_any_of = || {
|
||||
if any_of.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"unexpected fields 'anyOf' or 'oneOf'; applicators must singletons",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let expect_no_left_or_right = || {
|
||||
if left.is_some() || right.is_some() {
|
||||
return Err(de::Error::custom(
|
||||
"unexpected field(s) 'left' and/or 'right' for a non-pair data-type",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
match data_type {
|
||||
None => {
|
||||
expect_no_items()?;
|
||||
expect_no_keys()?;
|
||||
expect_no_values()?;
|
||||
expect_no_left_or_right()?;
|
||||
match any_of {
|
||||
None => Ok(Schema::Data(Data::Opaque)),
|
||||
Some(constructors) => Ok(Schema::Data(Data::AnyOf(constructors))),
|
||||
}
|
||||
}
|
||||
Some(data_type) if data_type == "list" => {
|
||||
expect_no_keys()?;
|
||||
expect_no_values()?;
|
||||
expect_no_any_of()?;
|
||||
expect_no_left_or_right()?;
|
||||
let items = expect_data_items()?;
|
||||
Ok(Schema::Data(Data::List(items)))
|
||||
}
|
||||
Some(data_type) if data_type == "#list" => {
|
||||
expect_no_keys()?;
|
||||
expect_no_values()?;
|
||||
expect_no_any_of()?;
|
||||
expect_no_left_or_right()?;
|
||||
let items = expect_schema_items()?;
|
||||
Ok(Schema::List(items))
|
||||
}
|
||||
Some(data_type) if data_type == "map" => {
|
||||
expect_no_items()?;
|
||||
expect_no_any_of()?;
|
||||
expect_no_left_or_right()?;
|
||||
match (keys, values) {
|
||||
(Some(keys), Some(values)) => Ok(Schema::Data(Data::Map(keys, values))),
|
||||
(None, _) => Err(de::Error::missing_field("keys")),
|
||||
(Some(..), None) => Err(de::Error::missing_field("values")),
|
||||
}
|
||||
}
|
||||
Some(data_type) if data_type == "#pair" => {
|
||||
expect_no_items()?;
|
||||
expect_no_keys()?;
|
||||
expect_no_values()?;
|
||||
expect_no_any_of()?;
|
||||
match (left, right) {
|
||||
(Some(left), Some(right)) => Ok(Schema::Pair(left, right)),
|
||||
(None, _) => Err(de::Error::missing_field("left")),
|
||||
(Some(..), None) => Err(de::Error::missing_field("right")),
|
||||
}
|
||||
}
|
||||
Some(data_type) => {
|
||||
expect_no_items()?;
|
||||
expect_no_keys()?;
|
||||
expect_no_values()?;
|
||||
expect_no_any_of()?;
|
||||
expect_no_left_or_right()?;
|
||||
if data_type == "bytes" {
|
||||
Ok(Schema::Data(Data::Bytes))
|
||||
} else if data_type == "integer" {
|
||||
Ok(Schema::Data(Data::Integer))
|
||||
} else if data_type == "#unit" {
|
||||
Ok(Schema::Unit)
|
||||
} else if data_type == "#integer" {
|
||||
Ok(Schema::Integer)
|
||||
} else if data_type == "#bytes" {
|
||||
Ok(Schema::Bytes)
|
||||
} else if data_type == "#boolean" {
|
||||
Ok(Schema::Boolean)
|
||||
} else if data_type == "#string" {
|
||||
Ok(Schema::String)
|
||||
} else {
|
||||
Err(de::Error::custom("unknown data-type"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Schema {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct SchemaVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> for SchemaVisitor {
|
||||
type Value = Schema;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Schema")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Schema, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
visit_schema(&mut map)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct(
|
||||
"Schema",
|
||||
&[
|
||||
"dataType", "items", "keys", "values", "anyOf", "oneOf", "left", "right",
|
||||
],
|
||||
SchemaVisitor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Data {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
|
@ -527,13 +806,7 @@ impl Serialize for Data {
|
|||
s.serialize_field("dataType", "bytes")?;
|
||||
s.end()
|
||||
}
|
||||
Data::List(Items::One(item)) => {
|
||||
let mut s = serializer.serialize_struct("List", 2)?;
|
||||
s.serialize_field("dataType", "list")?;
|
||||
s.serialize_field("items", &item)?;
|
||||
s.end()
|
||||
}
|
||||
Data::List(Items::Many(items)) => {
|
||||
Data::List(items) => {
|
||||
let mut s = serializer.serialize_struct("List", 2)?;
|
||||
s.serialize_field("dataType", "list")?;
|
||||
s.serialize_field("items", &items)?;
|
||||
|
@ -554,6 +827,38 @@ impl Serialize for Data {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Data {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct DataVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> for DataVisitor {
|
||||
type Value = Data;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Data")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Data, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let schema = visit_schema(&mut map)?;
|
||||
match schema {
|
||||
Schema::Data(data) => Ok(data),
|
||||
_ => Err(de::Error::custom("not a valid 'data'")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct(
|
||||
"Data",
|
||||
&["dataType", "items", "keys", "values", "anyOf", "oneOf"],
|
||||
DataVisitor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Constructor {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut s = serializer.serialize_struct("Constructor", 3)?;
|
||||
|
@ -564,6 +869,60 @@ impl Serialize for Constructor {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Deserialize<'a> for Constructor {
|
||||
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "camelCase")]
|
||||
enum Field {
|
||||
Index,
|
||||
Fields,
|
||||
}
|
||||
const FIELDS: &[&str] = &["index", "fields"];
|
||||
|
||||
struct ConstructorVisitor;
|
||||
|
||||
impl<'a> Visitor<'a> for ConstructorVisitor {
|
||||
type Value = Constructor;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Constructor")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<Constructor, V::Error>
|
||||
where
|
||||
V: MapAccess<'a>,
|
||||
{
|
||||
let mut index = None;
|
||||
let mut fields = None;
|
||||
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Index => {
|
||||
if index.is_some() {
|
||||
return Err(de::Error::duplicate_field(FIELDS[0]));
|
||||
}
|
||||
index = Some(map.next_value()?);
|
||||
}
|
||||
Field::Fields => {
|
||||
if fields.is_some() {
|
||||
return Err(de::Error::duplicate_field(FIELDS[1]));
|
||||
}
|
||||
fields = Some(map.next_value()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Constructor {
|
||||
index: index.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?,
|
||||
fields: fields.ok_or_else(|| de::Error::missing_field(FIELDS[1]))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct("Constructor", FIELDS, ConstructorVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, thiserror::Error)]
|
||||
#[error("{}", context)]
|
||||
pub struct Error {
|
||||
|
@ -681,6 +1040,7 @@ Here's the types I followed and that led me to this problem:
|
|||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
use serde_json::{self, json, Value};
|
||||
|
||||
pub fn assert_json(schema: &impl Serialize, expected: Value) {
|
||||
|
@ -712,7 +1072,7 @@ pub mod test {
|
|||
#[test]
|
||||
fn serialize_data_list_1() {
|
||||
let ref_integer = Reference::new("Int");
|
||||
let schema = Schema::Data(Data::List(Items::One(Box::new(ref_integer))));
|
||||
let schema = Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_integer))));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
|
@ -727,7 +1087,9 @@ pub mod test {
|
|||
#[test]
|
||||
fn serialize_data_list_2() {
|
||||
let ref_list_integer = Reference::new("List$Int");
|
||||
let schema = Schema::Data(Data::List(Items::One(Box::new(ref_list_integer))));
|
||||
let schema = Schema::Data(Data::List(Items::One(Declaration::Referenced(
|
||||
ref_list_integer,
|
||||
))));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
|
@ -741,9 +1103,9 @@ pub mod test {
|
|||
|
||||
#[test]
|
||||
fn serialize_data_map_1() {
|
||||
let ref_integer = Reference::new("Int");
|
||||
let ref_bytes = Reference::new("ByteArray");
|
||||
let schema = Schema::Data(Data::Map(Box::new(ref_integer), Box::new(ref_bytes)));
|
||||
let ref_integer = Declaration::Referenced(Reference::new("Int"));
|
||||
let ref_bytes = Declaration::Referenced(Reference::new("ByteArray"));
|
||||
let schema = Schema::Data(Data::Map(ref_integer, ref_bytes));
|
||||
assert_json(
|
||||
&schema,
|
||||
json!({
|
||||
|
@ -782,12 +1144,12 @@ pub mod test {
|
|||
let schema = Schema::Data(Data::AnyOf(vec![
|
||||
Constructor {
|
||||
index: 0,
|
||||
fields: vec![Reference::new("Int").into()],
|
||||
fields: vec![Declaration::Referenced(Reference::new("Int")).into()],
|
||||
}
|
||||
.into(),
|
||||
Constructor {
|
||||
index: 1,
|
||||
fields: vec![Reference::new("Bytes").into()],
|
||||
fields: vec![Declaration::Referenced(Reference::new("Bytes")).into()],
|
||||
}
|
||||
.into(),
|
||||
]));
|
||||
|
@ -848,4 +1210,224 @@ pub mod test {
|
|||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_opaque() {
|
||||
assert_eq!(Data::Opaque, serde_json::from_value(json!({})).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_integer() {
|
||||
assert_eq!(
|
||||
Data::Integer,
|
||||
serde_json::from_value(json!({
|
||||
"dataType": "integer",
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_bytes() {
|
||||
assert_eq!(
|
||||
Data::Bytes,
|
||||
serde_json::from_value(json!({
|
||||
"dataType": "bytes",
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_list_one() {
|
||||
assert_eq!(
|
||||
Data::List(Items::One(Declaration::Referenced(Reference::new("foo")))),
|
||||
serde_json::from_value(json!({
|
||||
"dataType": "list",
|
||||
"items": { "$ref": "#/definitions/foo" }
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_list_many() {
|
||||
assert_eq!(
|
||||
Data::List(Items::Many(vec![
|
||||
Declaration::Referenced(Reference::new("foo")),
|
||||
Declaration::Referenced(Reference::new("bar"))
|
||||
])),
|
||||
serde_json::from_value(json!({
|
||||
"dataType": "list",
|
||||
"items": [
|
||||
{ "$ref": "#/definitions/foo" },
|
||||
{ "$ref": "#/definitions/bar" }
|
||||
],
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_data_map() {
|
||||
assert_eq!(
|
||||
Data::Map(
|
||||
Declaration::Referenced(Reference::new("foo")),
|
||||
Declaration::Referenced(Reference::new("bar"))
|
||||
),
|
||||
serde_json::from_value(json!({
|
||||
"dataType": "map",
|
||||
"keys": { "$ref": "#/definitions/foo" },
|
||||
"values": { "$ref": "#/definitions/bar" }
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_any_of() {
|
||||
assert_eq!(
|
||||
Data::AnyOf(vec![Constructor {
|
||||
index: 0,
|
||||
fields: vec![
|
||||
Declaration::Referenced(Reference::new("foo")).into(),
|
||||
Declaration::Referenced(Reference::new("bar")).into()
|
||||
],
|
||||
}
|
||||
.into()]),
|
||||
serde_json::from_value(json!({
|
||||
"anyOf": [{
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"$ref": "#/definitions/foo",
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/bar",
|
||||
}
|
||||
]
|
||||
}]
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_one_of() {
|
||||
assert_eq!(
|
||||
Data::AnyOf(vec![Constructor {
|
||||
index: 0,
|
||||
fields: vec![
|
||||
Declaration::Referenced(Reference::new("foo")).into(),
|
||||
Declaration::Referenced(Reference::new("bar")).into()
|
||||
],
|
||||
}
|
||||
.into()]),
|
||||
serde_json::from_value(json!({
|
||||
"oneOf": [{
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"$ref": "#/definitions/foo",
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/bar",
|
||||
}
|
||||
]
|
||||
}]
|
||||
}))
|
||||
.unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
fn arbitrary_data() -> impl Strategy<Value = Data> {
|
||||
let leaf = prop_oneof![Just(Data::Opaque), Just(Data::Bytes), Just(Data::Integer)];
|
||||
|
||||
leaf.prop_recursive(3, 8, 3, |inner| {
|
||||
let r = prop_oneof![
|
||||
".*".prop_map(|s| Declaration::Referenced(Reference::new(&s))),
|
||||
inner.prop_map(|s| Declaration::Inline(Box::new(s)))
|
||||
];
|
||||
let constructor =
|
||||
(0..3usize, prop::collection::vec(r.clone(), 0..3)).prop_map(|(index, fields)| {
|
||||
Constructor {
|
||||
index,
|
||||
fields: fields.into_iter().map(|f| f.into()).collect(),
|
||||
}
|
||||
.into()
|
||||
});
|
||||
|
||||
prop_oneof![
|
||||
(r.clone(), r.clone()).prop_map(|(k, v)| Data::Map(k, v)),
|
||||
r.clone().prop_map(|x| Data::List(Items::One(x))),
|
||||
prop::collection::vec(r, 1..3).prop_map(|xs| Data::List(Items::Many(xs))),
|
||||
prop::collection::vec(constructor, 1..3).prop_map(Data::AnyOf)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
fn arbitrary_schema() -> impl Strategy<Value = Schema> {
|
||||
prop_compose! {
|
||||
fn data_strategy()(data in arbitrary_data()) -> Schema {
|
||||
Schema::Data(data)
|
||||
}
|
||||
}
|
||||
|
||||
let leaf = prop_oneof![
|
||||
Just(Schema::Unit),
|
||||
Just(Schema::Boolean),
|
||||
Just(Schema::Bytes),
|
||||
Just(Schema::Integer),
|
||||
Just(Schema::String),
|
||||
data_strategy(),
|
||||
];
|
||||
|
||||
leaf.prop_recursive(3, 8, 3, |inner| {
|
||||
let r = prop_oneof![
|
||||
".*".prop_map(|s| Declaration::Referenced(Reference::new(&s))),
|
||||
inner.prop_map(|s| Declaration::Inline(Box::new(s)))
|
||||
];
|
||||
prop_oneof![
|
||||
(r.clone(), r.clone()).prop_map(|(l, r)| Schema::Pair(l, r)),
|
||||
r.clone().prop_map(|x| Schema::List(Items::One(x))),
|
||||
prop::collection::vec(r, 1..3).prop_map(|xs| Schema::List(Items::Many(xs))),
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn data_serialization_roundtrip(data in arbitrary_data()) {
|
||||
let json = serde_json::to_value(data);
|
||||
let pretty = json
|
||||
.as_ref()
|
||||
.map(|v| serde_json::to_string_pretty(v).unwrap())
|
||||
.unwrap_or_else(|_| "invalid".to_string());
|
||||
assert!(
|
||||
matches!(
|
||||
json.and_then(serde_json::from_value::<Data>),
|
||||
Ok{..}
|
||||
),
|
||||
"\ncounterexample: {pretty}\n",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn schema_serialization_roundtrip(schema in arbitrary_schema()) {
|
||||
let json = serde_json::to_value(schema);
|
||||
let pretty = json
|
||||
.as_ref()
|
||||
.map(|v| serde_json::to_string_pretty(v).unwrap())
|
||||
.unwrap_or_else(|_| "invalid".to_string());
|
||||
assert!(
|
||||
matches!(
|
||||
json.and_then(serde_json::from_value::<Schema>),
|
||||
Ok{..}
|
||||
),
|
||||
"\ncounterexample: {pretty}\n",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::{
|
||||
definitions::{Definitions, Reference},
|
||||
definitions::Definitions,
|
||||
error::Error,
|
||||
parameter::Parameter,
|
||||
schema::{Annotated, Schema},
|
||||
};
|
||||
use crate::module::{CheckedModule, CheckedModules};
|
||||
|
@ -13,44 +14,36 @@ use serde;
|
|||
use uplc::ast::{DeBruijn, Program, Term};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Validator<R, S> {
|
||||
pub struct Validator {
|
||||
pub title: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub datum: Option<Argument<R>>,
|
||||
pub datum: Option<Parameter>,
|
||||
|
||||
pub redeemer: Argument<R>,
|
||||
pub redeemer: Parameter,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
pub parameters: Vec<Argument<R>>,
|
||||
pub parameters: Vec<Parameter>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub program: Program<DeBruijn>,
|
||||
|
||||
#[serde(skip_serializing_if = "Definitions::is_empty")]
|
||||
#[serde(default)]
|
||||
pub definitions: Definitions<S>,
|
||||
pub definitions: Definitions<Annotated<Schema>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Argument<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
|
||||
pub schema: T,
|
||||
}
|
||||
|
||||
impl Validator<Reference, Annotated<Schema>> {
|
||||
impl Validator {
|
||||
pub fn from_checked_module(
|
||||
modules: &CheckedModules,
|
||||
generator: &mut CodeGenerator,
|
||||
module: &CheckedModule,
|
||||
def: &TypedValidator,
|
||||
) -> Vec<Result<Validator<Reference, Annotated<Schema>>, Error>> {
|
||||
) -> Vec<Result<Validator, Error>> {
|
||||
let program = generator.generate(def).try_into().unwrap();
|
||||
|
||||
let is_multi_validator = def.other_fun.is_some();
|
||||
|
@ -85,7 +78,7 @@ impl Validator<Reference, Annotated<Schema>> {
|
|||
params: &[TypedArg],
|
||||
func: &TypedFunction,
|
||||
is_multi_validator: bool,
|
||||
) -> Result<Validator<Reference, Annotated<Schema>>, Error> {
|
||||
) -> Result<Validator, Error> {
|
||||
let mut args = func.arguments.iter().rev();
|
||||
let (_, redeemer, datum) = (args.next(), args.next().unwrap(), args.next());
|
||||
|
||||
|
@ -102,7 +95,7 @@ impl Validator<Reference, Annotated<Schema>> {
|
|||
.iter()
|
||||
.map(|param| {
|
||||
Annotated::from_type(modules.into(), ¶m.tipo, &mut definitions)
|
||||
.map(|schema| Argument {
|
||||
.map(|schema| Parameter {
|
||||
title: Some(param.arg_name.get_label()),
|
||||
schema,
|
||||
})
|
||||
|
@ -130,7 +123,7 @@ impl Validator<Reference, Annotated<Schema>> {
|
|||
)
|
||||
})
|
||||
.transpose()?
|
||||
.map(|schema| Argument {
|
||||
.map(|schema| Parameter {
|
||||
title: datum.map(|datum| datum.arg_name.get_label()),
|
||||
schema,
|
||||
}),
|
||||
|
@ -143,7 +136,7 @@ impl Validator<Reference, Annotated<Schema>> {
|
|||
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(
|
||||
|
@ -160,16 +153,16 @@ impl Validator<Reference, Annotated<Schema>> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<R, S> Validator<R, S>
|
||||
where
|
||||
S: Clone,
|
||||
R: Clone,
|
||||
{
|
||||
pub fn apply(self, arg: &Term<DeBruijn>) -> Result<Self, Error> {
|
||||
impl Validator {
|
||||
pub fn apply(
|
||||
self,
|
||||
definitions: &Definitions<Annotated<Schema>>,
|
||||
arg: &Term<DeBruijn>,
|
||||
) -> Result<Self, Error> {
|
||||
match self.parameters.split_first() {
|
||||
None => Err(Error::NoParametersToApply),
|
||||
Some((_, tail)) => {
|
||||
// TODO: Ideally, we should control that the applied term matches its schema.
|
||||
Some((head, tail)) => {
|
||||
head.validate(definitions, arg)?;
|
||||
Ok(Self {
|
||||
program: self.program.apply_term(arg),
|
||||
parameters: tail.to_vec(),
|
||||
|
@ -182,7 +175,14 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use super::{
|
||||
super::{
|
||||
definitions::{Definitions, Reference},
|
||||
error::Error,
|
||||
schema::{Annotated, Constructor, Data, Declaration, Items, Schema},
|
||||
},
|
||||
*,
|
||||
};
|
||||
use crate::{module::ParsedModule, PackageName};
|
||||
use aiken_lang::{
|
||||
self,
|
||||
|
@ -197,6 +197,7 @@ mod test {
|
|||
use indexmap::IndexMap;
|
||||
use serde_json::{self, json};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
use uplc::ast as uplc;
|
||||
|
||||
// TODO: Possible refactor this out of the module and have it used by `Project`. The idea would
|
||||
// be to make this struct below the actual project, and wrap it in another metadata struct
|
||||
|
@ -318,6 +319,69 @@ mod test {
|
|||
assert_json_eq!(serde_json::to_value(validator).unwrap(), expected);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mint_basic() {
|
||||
assert_validator(
|
||||
|
@ -1096,4 +1160,266 @@ mod test {
|
|||
}),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_arguments_integer() {
|
||||
let definitions = fixture_definitions();
|
||||
|
||||
let term = Term::data(uplc::Data::integer(42.into()));
|
||||
|
||||
let param = Parameter {
|
||||
title: None,
|
||||
schema: Reference::new("Int"),
|
||||
};
|
||||
|
||||
assert!(matches!(param.validate(&definitions, &term), Ok { .. }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_arguments_bytestring() {
|
||||
let definitions = fixture_definitions();
|
||||
|
||||
let term = Term::data(uplc::Data::bytestring(vec![102, 111, 111]));
|
||||
|
||||
let param = Parameter {
|
||||
title: None,
|
||||
schema: Reference::new("ByteArray"),
|
||||
};
|
||||
|
||||
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 { .. }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,7 @@ pub mod pretty;
|
|||
pub mod script;
|
||||
pub mod telemetry;
|
||||
|
||||
use crate::blueprint::{
|
||||
definitions::Reference,
|
||||
schema::{Annotated, Schema},
|
||||
Blueprint,
|
||||
};
|
||||
use crate::blueprint::Blueprint;
|
||||
use aiken_lang::{
|
||||
ast::{Definition, Function, ModuleKind, Tracing, TypedDataType, TypedFunction},
|
||||
builtins,
|
||||
|
@ -218,10 +214,7 @@ where
|
|||
self.compile(options)
|
||||
}
|
||||
|
||||
pub fn dump_uplc(
|
||||
&self,
|
||||
blueprint: &Blueprint<Reference, Annotated<Schema>>,
|
||||
) -> Result<(), Error> {
|
||||
pub fn dump_uplc(&self, blueprint: &Blueprint) -> Result<(), Error> {
|
||||
let dir = self.root.join("artifacts");
|
||||
|
||||
self.event_listener
|
||||
|
@ -362,8 +355,7 @@ where
|
|||
// Read blueprint
|
||||
let blueprint = File::open(self.blueprint_path())
|
||||
.map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?;
|
||||
let blueprint: Blueprint<serde_json::Value, serde_json::Value> =
|
||||
serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
let blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
|
||||
// Calculate the address
|
||||
let when_too_many =
|
||||
|
@ -386,12 +378,11 @@ where
|
|||
&self,
|
||||
title: Option<&String>,
|
||||
param: &Term<DeBruijn>,
|
||||
) -> Result<Blueprint<serde_json::Value, serde_json::Value>, Error> {
|
||||
) -> Result<Blueprint, Error> {
|
||||
// Read blueprint
|
||||
let blueprint = File::open(self.blueprint_path())
|
||||
.map_err(|_| blueprint::error::Error::InvalidOrMissingFile)?;
|
||||
let mut blueprint: Blueprint<serde_json::Value, serde_json::Value> =
|
||||
serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
let mut blueprint: Blueprint = serde_json::from_reader(BufReader::new(blueprint))?;
|
||||
|
||||
// Apply parameters
|
||||
let when_too_many =
|
||||
|
@ -400,7 +391,9 @@ where
|
|||
|
||||
let applied_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
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
use crate::with_project;
|
||||
use aiken_project::error::Error;
|
||||
use miette::IntoDiagnostic;
|
||||
use std::{fs, path::PathBuf};
|
||||
use uplc::{
|
||||
ast::{DeBruijn, Term},
|
||||
parser,
|
||||
};
|
||||
use aiken_project::{blueprint, error::Error};
|
||||
use owo_colors::{OwoColorize, Stream::Stderr};
|
||||
use std::{fs, path::PathBuf, process, rc::Rc};
|
||||
use uplc::ast::{Constant, DeBruijn, Term};
|
||||
|
||||
/// Apply a parameter to a parameterized validator.
|
||||
#[derive(clap::Args)]
|
||||
pub struct Args {
|
||||
/// The parameter, as a Plutus Data (CBOR, hex-encoded)
|
||||
parameter: String,
|
||||
|
||||
/// Path to project
|
||||
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.
|
||||
#[clap(short, long)]
|
||||
module: Option<String>,
|
||||
|
@ -20,23 +24,58 @@ pub struct Args {
|
|||
/// Name of the validator within the module. Optional if there's only one validator.
|
||||
#[clap(short, long)]
|
||||
validator: Option<String>,
|
||||
|
||||
/// The parameter, using high-level UPLC-syntax
|
||||
parameter: String,
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
Args {
|
||||
parameter,
|
||||
directory,
|
||||
out,
|
||||
module,
|
||||
validator,
|
||||
parameter,
|
||||
}: Args,
|
||||
) -> miette::Result<()> {
|
||||
let term: Term<DeBruijn> = parser::term(¶meter)
|
||||
.into_diagnostic()?
|
||||
.try_into()
|
||||
.into_diagnostic()?;
|
||||
eprintln!(
|
||||
"{} inputs",
|
||||
" Parsing"
|
||||
.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| {
|
||||
let title = module.as_ref().map(|m| {
|
||||
|
@ -51,16 +90,35 @@ pub fn exec(
|
|||
|
||||
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 json = serde_json::to_string_pretty(&blueprint).unwrap();
|
||||
|
||||
fs::write(p.blueprint_path(), json).map_err(|error| {
|
||||
Error::FileIo {
|
||||
match out {
|
||||
None => {
|
||||
println!("\n{}\n", json);
|
||||
Ok(())
|
||||
}
|
||||
Some(ref path) => fs::write(path, json).map_err(|error| Error::FileIo {
|
||||
error,
|
||||
path: p.blueprint_path(),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
" Done"
|
||||
.if_supports_color(Stderr, |s| s.purple())
|
||||
.if_supports_color(Stderr, |s| s.bold()),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ pub fn exec(
|
|||
.map_err(|_| BlueprintError::InvalidOrMissingFile)
|
||||
.into_diagnostic()?;
|
||||
|
||||
let blueprint: Blueprint<serde_json::Value, serde_json::Value> =
|
||||
let blueprint: Blueprint =
|
||||
serde_json::from_reader(BufReader::new(blueprint)).into_diagnostic()?;
|
||||
|
||||
// Perform the conversion
|
||||
|
|
|
@ -1,23 +1,3 @@
|
|||
use std::{
|
||||
fmt::{self, Display},
|
||||
hash::{self, Hash},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use serde::{
|
||||
self,
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
|
||||
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_primitives::{
|
||||
alonzo::PlutusData,
|
||||
babbage::{self as cardano, Language},
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
|
||||
use crate::{
|
||||
builtins::DefaultFunction,
|
||||
debruijn::{self, Converter},
|
||||
|
@ -28,6 +8,24 @@ use crate::{
|
|||
Machine,
|
||||
},
|
||||
};
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::ToPrimitive;
|
||||
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_primitives::{
|
||||
alonzo::{self as pallas, Constr, PlutusData},
|
||||
babbage::{self as cardano, Language},
|
||||
};
|
||||
use pallas_traverse::ComputeHash;
|
||||
use serde::{
|
||||
self,
|
||||
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||
ser::{Serialize, SerializeStruct, Serializer},
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
hash::{self, Hash},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// This represents a program in Untyped Plutus Core.
|
||||
/// A program contains a version tuple and a term.
|
||||
|
@ -239,6 +237,61 @@ pub enum Constant {
|
|||
Data(PlutusData),
|
||||
}
|
||||
|
||||
pub struct Data {}
|
||||
|
||||
// TODO: See about moving these builders upstream to Pallas?
|
||||
impl Data {
|
||||
pub fn integer(i: BigInt) -> PlutusData {
|
||||
match i.to_i64() {
|
||||
Some(i) => PlutusData::BigInt(pallas::BigInt::Int(i.into())),
|
||||
None => {
|
||||
let (sign, bytes) = i.to_bytes_be();
|
||||
match sign {
|
||||
num_bigint::Sign::Minus => {
|
||||
PlutusData::BigInt(pallas::BigInt::BigNInt(bytes.into()))
|
||||
}
|
||||
_ => PlutusData::BigInt(pallas::BigInt::BigUInt(bytes.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytestring(bytes: Vec<u8>) -> PlutusData {
|
||||
PlutusData::BoundedBytes(bytes.into())
|
||||
}
|
||||
|
||||
pub fn map(kvs: Vec<(PlutusData, PlutusData)>) -> PlutusData {
|
||||
PlutusData::Map(kvs.into())
|
||||
}
|
||||
|
||||
pub fn list(xs: Vec<PlutusData>) -> PlutusData {
|
||||
PlutusData::Array(xs)
|
||||
}
|
||||
|
||||
pub fn constr(ix: u64, fields: Vec<PlutusData>) -> PlutusData {
|
||||
// NOTE: see https://github.com/input-output-hk/plutus/blob/9538fc9829426b2ecb0628d352e2d7af96ec8204/plutus-core/plutus-core/src/PlutusCore/Data.hs#L139-L155
|
||||
if ix < 7 {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: 121 + ix,
|
||||
any_constructor: None,
|
||||
fields,
|
||||
})
|
||||
} else if ix < 128 {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: 1280 + ix - 7,
|
||||
any_constructor: None,
|
||||
fields,
|
||||
})
|
||||
} else {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: 102,
|
||||
any_constructor: Some(ix),
|
||||
fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Bool,
|
||||
|
|
|
@ -2,13 +2,14 @@ use crate::{
|
|||
ast::{Constant, Name, Term, Type},
|
||||
builtins::DefaultFunction,
|
||||
};
|
||||
use pallas_primitives::alonzo::PlutusData;
|
||||
|
||||
pub const CONSTR_FIELDS_EXPOSER: &str = "__constr_fields_exposer";
|
||||
pub const CONSTR_INDEX_EXPOSER: &str = "__constr_index_exposer";
|
||||
pub const CONSTR_GET_FIELD: &str = "__constr_get_field";
|
||||
pub const EXPECT_ON_LIST: &str = "__expect_on_list";
|
||||
|
||||
impl Term<Name> {
|
||||
impl<T> Term<T> {
|
||||
pub fn apply(self, arg: Self) -> Self {
|
||||
Term::Apply {
|
||||
function: self.into(),
|
||||
|
@ -16,13 +17,6 @@ impl Term<Name> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn lambda(self, parameter_name: impl ToString) -> Self {
|
||||
Term::Lambda {
|
||||
parameter_name: Name::text(parameter_name).into(),
|
||||
body: self.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force(self) -> Self {
|
||||
Term::Force(self.into())
|
||||
}
|
||||
|
@ -31,10 +25,6 @@ impl Term<Name> {
|
|||
Term::Delay(self.into())
|
||||
}
|
||||
|
||||
pub fn var(name: impl ToString) -> Self {
|
||||
Term::Var(Name::text(name).into())
|
||||
}
|
||||
|
||||
pub fn integer(i: num_bigint::BigInt) -> Self {
|
||||
Term::Constant(Constant::Integer(i).into())
|
||||
}
|
||||
|
@ -55,6 +45,10 @@ impl Term<Name> {
|
|||
Term::Constant(Constant::Unit.into())
|
||||
}
|
||||
|
||||
pub fn data(d: PlutusData) -> Self {
|
||||
Term::Constant(Constant::Data(d).into())
|
||||
}
|
||||
|
||||
pub fn empty_list() -> Self {
|
||||
Term::Constant(Constant::ProtoList(Type::Data, vec![]).into())
|
||||
}
|
||||
|
@ -204,7 +198,7 @@ impl Term<Name> {
|
|||
.force()
|
||||
}
|
||||
|
||||
pub fn trace(self, msg_term: Term<Name>) -> Self {
|
||||
pub fn trace(self, msg_term: Self) -> Self {
|
||||
Term::Builtin(DefaultFunction::Trace)
|
||||
.force()
|
||||
.apply(msg_term)
|
||||
|
@ -212,41 +206,34 @@ impl Term<Name> {
|
|||
.force()
|
||||
}
|
||||
|
||||
pub fn assert_on_list(self) -> Term<Name> {
|
||||
self.lambda(EXPECT_ON_LIST.to_string())
|
||||
.apply(
|
||||
Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())),
|
||||
)
|
||||
.lambda(EXPECT_ON_LIST.to_string())
|
||||
.apply(
|
||||
Term::var("__list_to_check".to_string())
|
||||
.delayed_choose_list(
|
||||
Term::unit(),
|
||||
Term::var("__check_with".to_string())
|
||||
.apply(
|
||||
Term::head_list().apply(Term::var("__list_to_check".to_string())),
|
||||
)
|
||||
.choose_unit(
|
||||
Term::var(EXPECT_ON_LIST.to_string())
|
||||
.apply(Term::var(EXPECT_ON_LIST.to_string()))
|
||||
.apply(
|
||||
Term::tail_list()
|
||||
.apply(Term::var("__list_to_check".to_string())),
|
||||
)
|
||||
.apply(Term::var("__check_with".to_string())),
|
||||
),
|
||||
)
|
||||
.lambda("__check_with".to_string())
|
||||
.lambda("__list_to_check".to_string())
|
||||
.lambda(EXPECT_ON_LIST),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn final_wrapper(self: Term<Name>) -> Term<Name> {
|
||||
pub fn final_wrapper(self) -> Self {
|
||||
self.delayed_if_else(Term::unit(), Term::Error)
|
||||
}
|
||||
|
||||
pub fn constr_fields_exposer(self: Term<Name>) -> Term<Name> {
|
||||
pub fn repeat_tail_list(self, repeat: usize) -> Self {
|
||||
let mut term = self;
|
||||
|
||||
for _ in 0..repeat {
|
||||
term = Term::tail_list().apply(term);
|
||||
}
|
||||
|
||||
term
|
||||
}
|
||||
}
|
||||
|
||||
impl Term<Name> {
|
||||
pub fn lambda(self, parameter_name: impl ToString) -> Self {
|
||||
Term::Lambda {
|
||||
parameter_name: Name::text(parameter_name).into(),
|
||||
body: self.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn var(name: impl ToString) -> Self {
|
||||
Term::Var(Name::text(name).into())
|
||||
}
|
||||
|
||||
pub fn constr_fields_exposer(self) -> Self {
|
||||
self.lambda(CONSTR_FIELDS_EXPOSER.to_string()).apply(
|
||||
Term::snd_pair()
|
||||
.apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string())))
|
||||
|
@ -254,7 +241,7 @@ impl Term<Name> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn constr_index_exposer(self: Term<Name>) -> Term<Name> {
|
||||
pub fn constr_index_exposer(self) -> Self {
|
||||
self.lambda(CONSTR_INDEX_EXPOSER.to_string()).apply(
|
||||
Term::fst_pair()
|
||||
.apply(Term::unconstr_data().apply(Term::var("__constr_var".to_string())))
|
||||
|
@ -262,7 +249,7 @@ impl Term<Name> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn constr_get_field(self: Term<Name>) -> Term<Name> {
|
||||
pub fn constr_get_field(self) -> Self {
|
||||
self.lambda(CONSTR_GET_FIELD.to_string())
|
||||
.apply(
|
||||
Term::var(CONSTR_GET_FIELD.to_string())
|
||||
|
@ -298,13 +285,33 @@ impl Term<Name> {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn repeat_tail_list(self: Term<Name>, repeat: usize) -> Term<Name> {
|
||||
let mut term = self;
|
||||
|
||||
for _ in 0..repeat {
|
||||
term = Term::tail_list().apply(term);
|
||||
}
|
||||
|
||||
term
|
||||
pub fn assert_on_list(self) -> Self {
|
||||
self.lambda(EXPECT_ON_LIST.to_string())
|
||||
.apply(
|
||||
Term::var(EXPECT_ON_LIST.to_string()).apply(Term::var(EXPECT_ON_LIST.to_string())),
|
||||
)
|
||||
.lambda(EXPECT_ON_LIST.to_string())
|
||||
.apply(
|
||||
Term::var("__list_to_check".to_string())
|
||||
.delayed_choose_list(
|
||||
Term::unit(),
|
||||
Term::var("__check_with".to_string())
|
||||
.apply(
|
||||
Term::head_list().apply(Term::var("__list_to_check".to_string())),
|
||||
)
|
||||
.choose_unit(
|
||||
Term::var(EXPECT_ON_LIST.to_string())
|
||||
.apply(Term::var(EXPECT_ON_LIST.to_string()))
|
||||
.apply(
|
||||
Term::tail_list()
|
||||
.apply(Term::var("__list_to_check".to_string())),
|
||||
)
|
||||
.apply(Term::var("__check_with".to_string())),
|
||||
),
|
||||
)
|
||||
.lambda("__check_with".to_string())
|
||||
.lambda("__list_to_check".to_string())
|
||||
.lambda(EXPECT_ON_LIST),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue