pub mod definitions; pub mod error; pub mod function; pub mod parameter; pub mod schema; pub mod validator; use crate::{config::Config, module::CheckedModules}; use aiken_lang::gen_uplc::CodeGenerator; use definitions::Definitions; use error::Error; use function::Function; use schema::{Annotated, Schema}; use std::fmt::Debug; use validator::Validator; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Blueprint { pub preamble: Preamble, pub validators: Vec, #[serde(skip_serializing_if = "Definitions::is_empty", default)] pub definitions: Definitions>, pub functions: Vec, } #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct Preamble { pub title: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, pub version: String, pub plutus_version: PlutusVersion, #[serde(skip_serializing_if = "Option::is_none")] pub license: Option, } #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub enum PlutusVersion { V1, V2, } #[derive(Debug, PartialEq, Clone)] pub enum LookupResult<'a, T> { One(&'a T), Many, } impl Blueprint { pub fn new( config: &Config, modules: &CheckedModules, generator: &mut CodeGenerator, ) -> Result { let preamble = config.into(); let mut definitions = Definitions::new(); let validators: Result, Error> = modules .validators() .flat_map(|(validator, def)| { Validator::from_checked_module(modules, generator, validator, def) .into_iter() .map(|result| { result.map(|mut schema| { definitions.merge(&mut schema.definitions); schema.definitions = Definitions::new(); schema }) }) .collect::>() }) .collect(); let functions: Result, Error> = modules .functions() .map(|(func, def)| { let mut f = Function::from_checked_module(modules, generator, func, def)?; definitions.merge(&mut f.definitions); f.definitions = Definitions::new(); Ok(f) }) .collect(); Ok(Blueprint { preamble, validators: validators?, functions: functions?, definitions, }) } } impl Blueprint { pub fn lookup(&self, title: Option<&String>) -> Option> { let mut validator = None; for v in self.validators.iter() { let match_title = Some(&v.title) == title.or(Some(&v.title)); if match_title { validator = Some(if validator.is_none() { LookupResult::One(v) } else { LookupResult::Many }) } } validator } pub fn with_validator( &self, title: Option<&String>, when_too_many: fn(Vec) -> E, when_missing: fn(Vec) -> E, action: F, ) -> Result where F: Fn(Validator) -> Result, { match self.lookup(title) { Some(LookupResult::One(validator)) => action(validator.to_owned()), Some(LookupResult::Many) => Err(when_too_many( self.validators.iter().map(|v| v.title.clone()).collect(), )), None => Err(when_missing( self.validators.iter().map(|v| v.title.clone()).collect(), )), } } } impl From<&Config> for Preamble { fn from(config: &Config) -> Self { Preamble { title: config.name.to_string(), description: if config.description.is_empty() { None } else { Some(config.description.clone()) }, plutus_version: PlutusVersion::V2, version: config.version.clone(), license: config.license.clone(), } } } #[cfg(test)] mod tests { use super::*; use aiken_lang::builtins; use schema::{Data, Declaration, Items, Schema}; use serde_json::{self, json}; use std::collections::HashMap; #[test] fn serialize_no_description() { let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: None, version: "1.0.0".to_string(), plutus_version: PlutusVersion::V2, license: Some("Apache-2.0".to_string()), }, validators: vec![], definitions: Definitions::new(), functions: vec![], }; assert_eq!( serde_json::to_value(&blueprint).unwrap(), json!({ "preamble": { "title": "Foo", "version": "1.0.0", "plutusVersion": "v2", "license": "Apache-2.0" }, "validators": [] }), ); } #[test] fn serialize_with_description() { let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: Some("Lorem ipsum".to_string()), version: "1.0.0".to_string(), plutus_version: PlutusVersion::V2, license: None, }, validators: vec![], definitions: Definitions::new(), functions: vec![], }; assert_eq!( serde_json::to_value(&blueprint).unwrap(), json!({ "preamble": { "title": "Foo", "description": "Lorem ipsum", "version": "1.0.0", "plutusVersion": "v2" }, "validators": [] }), ); } #[test] fn serialize_with_definitions() { let mut definitions = Definitions::new(); definitions .register::<_, Error>(&builtins::int(), &HashMap::new(), |_| { Ok(Schema::Data(Data::Integer).into()) }) .unwrap(); definitions .register::<_, Error>( &builtins::list(builtins::byte_array()), &HashMap::new(), |definitions| { let ref_bytes = definitions.register::<_, Error>( &builtins::byte_array(), &HashMap::new(), |_| Ok(Schema::Data(Data::Bytes).into()), )?; Ok( Schema::Data(Data::List(Items::One(Declaration::Referenced(ref_bytes)))) .into(), ) }, ) .unwrap(); let blueprint = Blueprint { preamble: Preamble { title: "Foo".to_string(), description: None, version: "1.0.0".to_string(), plutus_version: PlutusVersion::V2, license: None, }, validators: vec![], definitions, functions: vec![], }; assert_eq!( serde_json::to_value(&blueprint).unwrap(), json!({ "preamble": { "title": "Foo", "version": "1.0.0", "plutusVersion": "v2" }, "validators": [], "definitions": { "ByteArray": { "dataType": "bytes" }, "Int": { "dataType": "integer" }, "List$ByteArray": { "dataType": "list", "items": { "$ref": "#/definitions/ByteArray" } } } }), ); } }