Refactor blueprint & handle annotated schemas

This also now introduce two levels of representable types (because it's needed at least for tuples):

  Plutus Data (a.k.a Data) and UPLC primitives / constants (a.k.a Schema).

  In practice, we don't want to specify blueprints that use direct UPLC primitives because there's little support for producing those in the ecosystem. So we should aim for producing only Data whenever we can. Yet we don't want to forbid it either in case people know what they're doing. Which means that we need to capture that difference well in the type modelling (in Rust and in the CIP-0057 specification).

  I've also simplified the error type for now, just to provide some degree of feedback while working on this. I'll refine it later with proper errors.
This commit is contained in:
KtorZ 2023-01-28 13:18:59 +01:00
parent 547696abde
commit b93e14659c
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
4 changed files with 430 additions and 231 deletions

View File

@ -2,28 +2,36 @@ use super::schema;
use crate::module::CheckedModule;
use aiken_lang::ast::{Span, TypedFunction};
use miette::{Diagnostic, NamedSource};
use owo_colors::OwoColorize;
use std::{fmt::Debug, path::PathBuf};
#[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error {
#[error("Validator functions must return Bool")]
#[error("A validator functions must return Bool")]
#[diagnostic(code("aiken::blueprint::invalid::return_type"))]
ValidatorMustReturnBool {
path: PathBuf,
src: String,
#[source_code]
named: NamedSource,
#[label("invalid return type")]
location: Span,
},
#[error("Validator\n\n{name}\n\nrequires at least {at_least} arguments")]
#[error("A {} validator requires at least {at_least} arguments", name.purple().bold())]
#[diagnostic(code("aiken::blueprint::invalid::arity"))]
WrongValidatorArity {
name: String,
at_least: u8,
#[label("not enough arguments")]
location: Span,
path: PathBuf,
src: String,
#[source_code]
named: NamedSource,
},
#[error(transparent)]
Schema(schema::Error),
#[diagnostic(transparent)]
Schema(#[diagnostic_source] schema::Error),
}
pub fn assert_return_bool(module: &CheckedModule, def: &TypedFunction) -> Result<(), Error> {

View File

@ -4,10 +4,9 @@ pub mod validator;
use crate::{config::Config, module::CheckedModules};
use aiken_lang::uplc::CodeGenerator;
use error::*;
use schema::NamedSchema;
use error::Error;
use std::fmt::Debug;
use validator::{Purpose, Validator};
use validator::Validator;
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
pub struct Blueprint {
@ -21,47 +20,18 @@ impl Blueprint {
modules: &CheckedModules,
generator: &mut CodeGenerator,
) -> Result<Self, Error> {
let mut validators = Vec::new();
let preamble = Preamble::from_config(config);
for (validator, def) in modules.validators() {
let purpose: Purpose = def.name.clone().into();
assert_return_bool(validator, def)?;
assert_min_arity(validator, def, purpose.min_arity())?;
let mut args = def.arguments.iter().rev();
let (_, redeemer, datum) = (args.next(), args.next().unwrap(), args.next());
validators.push(Validator {
title: validator.name.clone(),
description: None,
purpose,
datum: datum
.map(|datum| {
NamedSchema::from_type(
modules.into(),
&datum.arg_name.get_label(),
&datum.tipo,
)
.map_err(Error::Schema)
})
.transpose()?,
redeemer: NamedSchema::from_type(
modules.into(),
&redeemer.arg_name.get_label(),
&redeemer.tipo,
)
.map_err(Error::Schema)?,
program: generator
.generate(&def.body, &def.arguments, true)
.try_into()
.unwrap(),
});
}
let validators: Result<Vec<_>, Error> = modules
.validators()
.map(|(validator, def)| {
Validator::from_checked_module(modules, generator, validator, def)
})
.collect();
Ok(Blueprint {
preamble: Preamble::from_config(config),
validators,
preamble,
validators: validators?,
})
}
}

View File

@ -1,14 +1,16 @@
use crate::CheckedModule;
use aiken_lang::{
ast::{DataType, Definition, TypedDefinition},
tipo::Type,
tipo::{pretty, Type, TypeVar},
};
use miette::Diagnostic;
use owo_colors::OwoColorize;
use serde::{
self,
ser::{Serialize, SerializeStruct, Serializer},
};
use serde_json;
use std::ops::Deref;
use std::{
collections::HashMap,
fmt::{self, Display},
@ -16,53 +18,157 @@ use std::{
};
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct NamedSchema {
pub title: String,
pub struct Annotated<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(flatten)]
pub schema: Schema,
pub annotated: T,
}
/// A schema for low-level UPLC primitives.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Schema {
Unit,
Boolean,
Integer,
Bytes,
List(Item<Box<Schema>>),
Map((Box<Schema>, Box<Schema>)),
AnyOf(Vec<Constructor>),
String,
Pair(Box<Data>, Box<Data>),
List(Box<Data>),
Data(Option<Data>),
}
/// A schema for Plutus' Data.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Data {
Integer,
Bytes,
List(Box<Data>),
Map(Box<Data>, Box<Data>),
AnyOf(Vec<Annotated<Constructor>>),
}
/// Captures a single UPLC constructor with its
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Constructor {
pub index: usize,
pub fields: Vec<NamedSchema>,
pub fields: Vec<Annotated<Data>>,
}
impl NamedSchema {
impl<T> From<T> for Annotated<T> {
fn from(annotated: T) -> Self {
Annotated {
title: None,
description: None,
annotated,
}
}
}
impl Annotated<Schema> {
pub fn from_type(
modules: &HashMap<String, CheckedModule>,
name: &str,
type_info: &Type,
) -> Result<Self, Error> {
match type_info {
Type::App {
module: module_name,
name: type_name,
args,
..
} if module_name.is_empty() => match &type_name[..] {
"ByteArray" => Ok(NamedSchema {
title: name.to_string(),
description: None,
schema: Schema::Bytes,
"Data" => Ok(Annotated {
title: Some("Data".to_string()),
description: Some("Any Plutus data.".to_string()),
annotated: Schema::Data(None),
}),
"Integer" => Ok(NamedSchema {
title: name.to_string(),
"ByteArray" => Ok(Annotated {
title: None,
description: None,
schema: Schema::Bytes,
annotated: Schema::Data(Some(Data::Bytes)),
}),
_ => Err(Error::UnsupportedPrimitiveType {
type_name: type_name.clone(),
"Int" => Ok(Annotated {
title: None,
description: None,
annotated: Schema::Data(Some(Data::Integer)),
}),
// TODO: Check whether this matches with the UPLC code generation as there are two
// options here since there's technically speaking a `unit` constant constructor in
// the UPLC primitives.
"Void" => Ok(Annotated {
title: Some("Unit".to_string()),
description: Some("The nullary constructor.".to_string()),
annotated: Schema::Data(Some(Data::AnyOf(vec![Annotated {
title: None,
description: None,
annotated: Constructor {
index: 0,
fields: vec![],
},
}]))),
}),
// TODO: Also check here whether this matches with the UPLC code generation.
"Bool" => Ok(Annotated {
title: Some("Bool".to_string()),
description: None,
annotated: Schema::Data(Some(Data::AnyOf(vec![
Annotated {
title: Some("False".to_string()),
description: None,
annotated: Constructor {
index: 0,
fields: vec![],
},
},
Annotated {
title: Some("True".to_string()),
description: None,
annotated: Constructor {
index: 1,
fields: vec![],
},
},
]))),
}),
"Option" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap())
.and_then(|s| s.into_data(type_info))?;
Ok(Annotated {
title: Some("Optional".to_string()),
description: None,
annotated: Schema::Data(Some(Data::AnyOf(vec![
Annotated {
title: Some("Some".to_string()),
description: Some("An optional value.".to_string()),
annotated: Constructor {
index: 0,
fields: vec![generic],
},
},
Annotated {
title: Some("None".to_string()),
description: Some("Nothing.".to_string()),
annotated: Constructor {
index: 1,
fields: vec![],
},
},
]))),
})
}
"List" => {
let generic = Annotated::from_type(modules, args.get(0).unwrap())
.and_then(|s| s.into_data(type_info))?;
Ok(Annotated {
title: None,
description: None,
annotated: Schema::Data(Some(Data::List(Box::new(generic.annotated)))),
})
}
_ => Err(Error::UnsupportedType {
type_info: type_info.clone(),
}),
},
Type::App {
@ -72,24 +178,47 @@ impl NamedSchema {
} => {
let module = modules.get(module_name).unwrap();
let constructor = find_definition(type_name, &module.ast.definitions).unwrap();
let schema = Schema::from_data_type(modules, constructor)?;
Ok(NamedSchema {
title: constructor.name.clone(),
let annotated = Schema::Data(Some(Data::from_data_type(modules, constructor)?));
Ok(Annotated {
title: Some(constructor.name.clone()),
description: constructor.doc.clone().map(|s| s.trim().to_string()),
schema,
annotated,
})
}
Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => {
Err(Error::UnsupportedKind {
arg_or_field_name: name.to_string(),
Type::Var { tipo } => match tipo.borrow().deref() {
TypeVar::Link { tipo } => Annotated::from_type(modules, tipo),
TypeVar::Generic { .. } => todo!(),
TypeVar::Unbound { .. } => Err(Error::UnsupportedType {
type_info: type_info.clone(),
})
}
}),
},
Type::Tuple { .. } => todo!(),
Type::Fn { .. } => Err(Error::UnsupportedType {
type_info: type_info.clone(),
}),
}
}
fn into_data(self, type_info: &Type) -> Result<Annotated<Data>, Error> {
match self {
Annotated {
title,
description,
annotated: Schema::Data(Some(data)),
} => Ok(Annotated {
title,
description,
annotated: data,
}),
_ => Err(Error::UnsupportedType {
type_info: type_info.to_owned(),
}),
}
}
}
impl Schema {
impl Data {
pub fn from_data_type(
modules: &HashMap<String, CheckedModule>,
data_type: &DataType<Arc<Type>>,
@ -98,17 +227,29 @@ impl Schema {
for (index, constructor) in data_type.constructors.iter().enumerate() {
let mut fields = vec![];
for field in constructor.arguments.iter() {
let mut schema = NamedSchema::from_type(
modules,
&field.label.clone().unwrap_or_default(),
&field.tipo,
)?;
schema.description = field.doc.clone().map(|s| s.trim().to_string());
let mut schema = Annotated::from_type(modules, &field.tipo)
.and_then(|t| t.into_data(&field.tipo))?;
if field.label.is_some() {
schema.title = field.label.clone();
}
if field.doc.is_some() {
schema.description = field.doc.clone().map(|s| s.trim().to_string());
}
fields.push(schema);
}
variants.push(Constructor { index, fields });
let variant = Annotated {
title: Some(constructor.name.clone()),
description: constructor.doc.clone(),
annotated: Constructor { index, fields },
};
variants.push(variant);
}
Ok(Schema::AnyOf(variants))
Ok(Data::AnyOf(variants))
}
}
@ -122,36 +263,89 @@ impl Display for Schema {
impl Serialize for Schema {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Schema::Unit => {
let mut s = serializer.serialize_struct("Unit", 1)?;
s.serialize_field("dataType", "#unit")?;
s.end()
}
Schema::Boolean => {
let mut s = serializer.serialize_struct("Integer", 1)?;
s.serialize_field("dataType", "#integer")?;
s.end()
}
Schema::Integer => {
let mut s = serializer.serialize_struct("Integer", 1)?;
s.serialize_field("dataType", "integer")?;
s.serialize_field("dataType", "#integer")?;
s.end()
}
Schema::Bytes => {
let mut s = serializer.serialize_struct("Bytes", 1)?;
s.serialize_field("dataType", "#bytes")?;
s.end()
}
Schema::String => {
let mut s = serializer.serialize_struct("String", 1)?;
s.serialize_field("dataType", "#string")?;
s.end()
}
Schema::Pair(left, right) => {
let mut s = serializer.serialize_struct("Pair", 3)?;
s.serialize_field("dataType", "#pair")?;
s.serialize_field("left", &left)?;
s.serialize_field("right", &right)?;
s.end()
}
Schema::List(elements) => {
let mut s = serializer.serialize_struct("List", 2)?;
s.serialize_field("dataType", "#list")?;
s.serialize_field("elements", &elements)?;
s.end()
}
Schema::Data(None) => {
let s = serializer.serialize_struct("Data", 0)?;
s.end()
}
Schema::Data(Some(data)) => data.serialize(serializer),
}
}
}
impl Serialize for Data {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Data::Integer => {
let mut s = serializer.serialize_struct("Integer", 1)?;
s.serialize_field("dataType", "integer")?;
s.end()
}
Data::Bytes => {
let mut s = serializer.serialize_struct("Bytes", 1)?;
s.serialize_field("dataType", "bytes")?;
s.end()
}
Schema::List(items) => {
Data::List(items) => {
let mut s = serializer.serialize_struct("List", 2)?;
s.serialize_field("dataType", "list")?;
s.serialize_field("items", &items)?;
s.end()
}
Schema::Map(elements) => {
let mut s = serializer.serialize_struct("Map", 2)?;
Data::Map(keys, values) => {
let mut s = serializer.serialize_struct("Map", 3)?;
s.serialize_field("dataType", "map")?;
s.serialize_field("elements", &elements)?;
s.serialize_field("keys", &keys)?;
s.serialize_field("values", &values)?;
s.end()
}
Data::AnyOf(constructors) => {
// TODO: Avoid 'anyOf' applicator when there's only one constructor
//
// match &constructors[..] {
// [constructor] => constructor.serialize(serializer),
// _ => {
let mut s = serializer.serialize_struct("AnyOf", 1)?;
s.serialize_field("anyOf", &constructors)?;
s.end()
}
Schema::AnyOf(constructors) => match &constructors[..] {
[constructor] => constructor.serialize(serializer),
_ => {
let mut s = serializer.serialize_struct("AnyOf", 1)?;
s.serialize_field("anyOf", &constructors)?;
s.end()
}
},
}
}
}
@ -165,32 +359,17 @@ impl Serialize for Constructor {
}
}
// Represent a items list in a JSON schema. Can be either a singleton (i.e. a single schema) when
// all elements in the list are uniform or a list of schemas.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Item<T> {
Singleton(T),
Many(Vec<T>),
}
impl<T: Serialize> Serialize for Item<T> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Item::Singleton(elem) => Serialize::serialize(elem, serializer),
Item::Many(elems) => Serialize::serialize(elems, serializer),
}
}
}
#[derive(Debug, PartialEq, Clone, thiserror::Error, Diagnostic)]
pub enum Error {
#[error("I stumble upon an unsupported kind in a datum or redeemer definition.\n")]
UnsupportedKind {
arg_or_field_name: String,
type_info: Type,
},
#[error("I discovered an unexpected primitive in a datum or redeemer definition.\n")]
UnsupportedPrimitiveType { type_name: String },
#[error("I stumbled upon an unsupported type in a datum or redeemer definition.")]
#[diagnostic(help(
r#"I do not know how to generate a portable Plutus specification for the following type:
{type_signature}
"#
, type_signature = pretty::Printer::new().print(type_info).to_pretty_string(70).bright_blue()
))]
UnsupportedType { type_info: Type },
}
fn find_definition<'a>(
@ -221,8 +400,8 @@ pub mod test {
}
#[test]
fn serialize_integer() {
let schema = Schema::Integer;
fn serialize_data_integer() {
let schema = Schema::Data(Some(Data::Integer));
assert_json(
&schema,
json!({
@ -232,8 +411,8 @@ pub mod test {
}
#[test]
fn serialize_bytes() {
let schema = Schema::Bytes;
fn serialize_data_bytes() {
let schema = Schema::Data(Some(Data::Bytes));
assert_json(
&schema,
json!({
@ -243,20 +422,8 @@ pub mod test {
}
#[test]
fn serialize_list_1() {
let schema = Schema::List(Item::Many(vec![]));
assert_json(
&schema,
json!({
"dataType": "list",
"items": []
}),
);
}
#[test]
fn serialize_list_2() {
let schema = Schema::List(Item::Singleton(Box::new(Schema::Integer)));
fn serialize_data_list_1() {
let schema = Schema::Data(Some(Data::List(Box::new(Data::Integer))));
assert_json(
&schema,
json!({
@ -269,98 +436,97 @@ pub mod test {
}
#[test]
fn serialize_list_3() {
let schema = Schema::List(Item::Many(vec![
Box::new(Schema::Bytes),
Box::new(Schema::List(Item::Singleton(Box::new(Schema::Integer)))),
]));
fn serialize_data_list_2() {
let schema = Schema::Data(Some(Data::List(Box::new(Data::List(Box::new(
Data::Integer,
))))));
assert_json(
&schema,
json!({
"dataType": "list",
"items": [
{
"dataType": "bytes"
},
"items":
{
"dataType": "list",
"items": { "dataType": "integer" }
}
]
}),
);
}
#[test]
fn serialize_map_1() {
let schema = Schema::Map((Box::new(Schema::Integer), Box::new(Schema::Bytes)));
fn serialize_data_map_1() {
let schema = Schema::Data(Some(Data::Map(
Box::new(Data::Integer),
Box::new(Data::Bytes),
)));
assert_json(
&schema,
json!({
"dataType": "map",
"elements": [
{
"dataType": "integer"
},
{
"dataType": "bytes"
}
]
"keys": {
"dataType": "integer"
},
"values": {
"dataType": "bytes"
}
}),
)
}
#[test]
fn serialize_map_2() {
let schema = Schema::Map((
Box::new(Schema::Bytes),
Box::new(Schema::List(Item::Singleton(Box::new(Schema::Integer)))),
));
fn serialize_data_map_2() {
let schema = Schema::Data(Some(Data::Map(
Box::new(Data::Bytes),
Box::new(Data::List(Box::new(Data::Integer))),
)));
assert_json(
&schema,
json!({
"dataType": "map",
"elements": [
{
"dataType": "bytes"
},
{
"dataType": "list",
"items": { "dataType": "integer" }
}
]
"keys": {
"dataType": "bytes"
},
"values": {
"dataType": "list",
"items": { "dataType": "integer" }
}
}),
)
}
#[test]
fn serialize_constr_1() {
let schema = Schema::AnyOf(vec![Constructor {
fn serialize_data_constr_1() {
let schema = Schema::Data(Some(Data::AnyOf(vec![Constructor {
index: 0,
fields: vec![],
}]);
}
.into()])));
assert_json(
&schema,
json!({
"dataType": "constructor",
"index": 0,
"fields": []
"anyOf": [{
"dataType": "constructor",
"index": 0,
"fields": []
}]
}),
)
}
#[test]
fn serialize_constr_2() {
let schema = Schema::AnyOf(vec![
fn serialize_data_constr_2() {
let schema = Schema::Data(Some(Data::AnyOf(vec![
Constructor {
index: 0,
fields: vec![Schema::Integer],
},
fields: vec![Data::Integer.into()],
}
.into(),
Constructor {
index: 1,
fields: vec![Schema::Bytes],
},
]);
fields: vec![Data::Bytes.into()],
}
.into(),
])));
assert_json(
&schema,
json!({
@ -381,34 +547,40 @@ pub mod test {
}
#[test]
fn serialize_named_no_description() {
let schema = NamedSchema {
title: "foo".to_string(),
fn serialize_empty_data() {
let schema = Schema::Data(None);
assert_json(&schema, json!({}))
}
#[test]
fn serialize_annotated_1() {
let schema = Annotated {
title: Some("foo".to_string()),
description: None,
schema: Schema::Integer,
annotated: Schema::Integer,
};
assert_json(
&schema,
json!({
"title": "foo",
"dataType": "integer"
"dataType": "#integer"
}),
)
}
#[test]
fn serialize_named_description() {
let schema = NamedSchema {
title: "foo".to_string(),
fn serialize_annotated_2() {
let schema = Annotated {
title: Some("foo".to_string()),
description: Some("Lorem Ipsum".to_string()),
schema: Schema::Integer,
annotated: Schema::String,
};
assert_json(
&schema,
json!({
"title": "foo",
"description": "Lorem Ipsum",
"dataType": "integer"
"dataType": "#string"
}),
)
}

View File

@ -1,4 +1,9 @@
use super::schema::NamedSchema;
use super::{
error::{assert_min_arity, assert_return_bool, Error},
schema::{Annotated, Schema},
};
use crate::module::{CheckedModule, CheckedModules};
use aiken_lang::{ast::TypedFunction, uplc::CodeGenerator};
use pallas::ledger::primitives::babbage as cardano;
use pallas_traverse::ComputeHash;
use serde::{
@ -13,19 +18,35 @@ pub struct Validator {
pub title: String,
pub purpose: Purpose,
pub description: Option<String>,
pub datum: Option<NamedSchema>,
pub redeemer: NamedSchema,
pub datum: Option<Annotated<Schema>>,
pub redeemer: Annotated<Schema>,
pub program: Program<NamedDeBruijn>,
}
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Purpose {
Spend,
Mint,
Withdraw,
Publish,
}
impl Serialize for Validator {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let cbor = self.program.to_cbor().unwrap();
let source_code = hex::encode(&cbor);
let mut s = serializer.serialize_struct("Validator", 5)?;
let hash = cardano::PlutusV2Script(cbor.into()).compute_hash();
let fields = 5
+ self.description.as_ref().map(|_| 1).unwrap_or_default()
+ self.datum.as_ref().map(|_| 1).unwrap_or_default();
let mut s = serializer.serialize_struct("Validator", fields)?;
s.serialize_field("title", &self.title)?;
s.serialize_field("purpose", &self.purpose)?;
let hash = cardano::PlutusV2Script(cbor.into()).compute_hash();
s.serialize_field("hash", &hash)?;
if let Some { .. } = self.description {
s.serialize_field("description", &self.description)?;
@ -39,13 +60,38 @@ impl Serialize for Validator {
}
}
#[derive(Debug, PartialEq, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Purpose {
Spend,
Mint,
Withdraw,
Publish,
impl Validator {
pub fn from_checked_module(
modules: &CheckedModules,
generator: &mut CodeGenerator,
validator: &CheckedModule,
def: &TypedFunction,
) -> Result<Validator, Error> {
let purpose: Purpose = def.name.clone().into();
assert_return_bool(validator, def)?;
assert_min_arity(validator, def, purpose.min_arity())?;
let mut args = def.arguments.iter().rev();
let (_, redeemer, datum) = (args.next(), args.next().unwrap(), args.next());
Ok(Validator {
title: validator.name.clone(),
description: None,
purpose,
datum: datum
.map(|datum| {
Annotated::from_type(modules.into(), &datum.tipo).map_err(Error::Schema)
})
.transpose()?,
redeemer: Annotated::from_type(modules.into(), &redeemer.tipo)
.map_err(Error::Schema)?,
program: generator
.generate(&def.body, &def.arguments, true)
.try_into()
.unwrap(),
})
}
}
impl Purpose {
@ -57,18 +103,6 @@ impl Purpose {
}
}
impl From<String> for Purpose {
fn from(purpose: String) -> Purpose {
match &purpose[..] {
"spend" => Purpose::Spend,
"mint" => Purpose::Mint,
"withdraw" => Purpose::Withdraw,
"publish" => Purpose::Publish,
unexpected => panic!("Can't turn '{}' into any Purpose", unexpected),
}
}
}
impl Display for Purpose {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
@ -80,9 +114,21 @@ impl Display for Purpose {
}
}
impl From<String> for Purpose {
fn from(purpose: String) -> Purpose {
match &purpose[..] {
"spend" => Purpose::Spend,
"mint" => Purpose::Mint,
"withdraw" => Purpose::Withdraw,
"publish" => Purpose::Publish,
unexpected => panic!("Can't turn '{}' into any Purpose", unexpected),
}
}
}
#[cfg(test)]
mod test {
use super::super::schema::{Constructor, Schema};
use super::super::schema::{Constructor, Data, Schema};
use super::*;
use serde_json::{self, json};
use uplc::parser;
@ -98,13 +144,14 @@ mod test {
description: Some("Lorem ipsum".to_string()),
purpose: Purpose::Spend,
datum: None,
redeemer: NamedSchema {
title: "Bar".to_string(),
redeemer: Annotated {
title: Some("Bar".to_string()),
description: None,
schema: Schema::AnyOf(vec![Constructor {
annotated: Schema::Data(Some(Data::AnyOf(vec![Constructor {
index: 0,
fields: vec![Schema::Bytes],
}]),
fields: vec![Data::Bytes.into()],
}
.into()]))),
},
program,
};
@ -112,18 +159,20 @@ mod test {
serde_json::to_value(&validator).unwrap(),
json!({
"title": "foo",
"description": "Lorem ipsum",
"purpose": "spend",
"hash": "27dc8e44c17b4ae5f4b9286ab599fffe70e61b49dec61eaca1fc5898",
"description": "Lorem ipsum",
"redeemer": {
"title": "Bar",
"dataType": "constructor",
"index": 0,
"fields": [{
"dataType": "bytes"
}]
"anyOf": [{
"dataType": "constructor",
"index": 0,
"fields": [{
"dataType": "bytes"
}]
}],
},
"compiledCode": "46010000481501",
"hash": "27dc8e44c17b4ae5f4b9286ab599fffe70e61b49dec61eaca1fc5898"
"compiledCode": "46010000481501"
}),
);
}