pub mod error; pub mod schema; pub mod validator; use crate::{config::Config, module::CheckedModules}; use aiken_lang::uplc::CodeGenerator; use error::Error; use schema::Schema; use std::fmt::{self, Debug, Display}; use validator::Validator; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub struct Blueprint { pub preamble: Preamble, pub validators: 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 validators: Result, Error> = modules .validators() .map(|(validator, def)| { Validator::from_checked_module(modules, generator, validator, def) }) .collect(); Ok(Blueprint { preamble, validators: validators?, }) } } impl Blueprint where T: Clone + Default, { 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 Display for Blueprint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?; f.write_str(&s) } } 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 test { use super::*; use serde_json::{self, json}; #[test] fn serialize_no_description() { let blueprint: 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![], }; 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 = 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![], }; assert_eq!( serde_json::to_value(&blueprint).unwrap(), json!({ "preamble": { "title": "Foo", "description": "Lorem ipsum", "version": "1.0.0", "plutusVersion": "v2" }, "validators": [] }), ); } }