diff --git a/crates/aiken-project/src/blueprint/error.rs b/crates/aiken-project/src/blueprint/error.rs index 16af5caa..8219da9f 100644 --- a/crates/aiken-project/src/blueprint/error.rs +++ b/crates/aiken-project/src/blueprint/error.rs @@ -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> { diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index faccec00..10434b88 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -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 { - 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, 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?, }) } } diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index f9e7898b..6a370c75 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -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 { + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[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>), - Map((Box, Box)), - AnyOf(Vec), + String, + Pair(Box, Box), + List(Box), + Data(Option), } +/// A schema for Plutus' Data. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Data { + Integer, + Bytes, + List(Box), + Map(Box, Box), + AnyOf(Vec>), +} + +/// Captures a single UPLC constructor with its #[derive(Debug, PartialEq, Eq, Clone)] pub struct Constructor { pub index: usize, - pub fields: Vec, + pub fields: Vec>, } -impl NamedSchema { +impl From for Annotated { + fn from(annotated: T) -> Self { + Annotated { + title: None, + description: None, + annotated, + } + } +} + +impl Annotated { pub fn from_type( modules: &HashMap, - name: &str, type_info: &Type, ) -> Result { 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, 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, data_type: &DataType>, @@ -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(&self, serializer: S) -> Result { 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(&self, serializer: S) -> Result { + 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 { - Singleton(T), - Many(Vec), -} - -impl Serialize for Item { - fn serialize(&self, serializer: S) -> Result { - 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" }), ) } diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 059eebdb..00ee99ca 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -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, - pub datum: Option, - pub redeemer: NamedSchema, + pub datum: Option>, + pub redeemer: Annotated, pub program: Program, } +#[derive(Debug, PartialEq, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub enum Purpose { + Spend, + Mint, + Withdraw, + Publish, +} + impl Serialize for Validator { fn serialize(&self, serializer: S) -> Result { 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 { + 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 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 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" }), ); }