aiken/crates/aiken-project/src/blueprint/mod.rs

338 lines
10 KiB
Rust

pub mod definitions;
pub mod error;
mod memo_program;
pub mod parameter;
pub mod schema;
pub mod validator;
use crate::{
config::{self, Config, PlutusVersion},
module::CheckedModules,
};
use aiken_lang::gen_uplc::CodeGenerator;
use definitions::Definitions;
pub use error::Error;
use schema::{Annotated, Schema};
use std::{collections::BTreeSet, fmt::Debug};
use validator::Validator;
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct Blueprint {
pub preamble: Preamble,
pub validators: Vec<Validator>,
#[serde(skip_serializing_if = "Definitions::is_empty", default)]
pub definitions: Definitions<Annotated<Schema>>,
}
#[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<String>,
pub version: String,
pub plutus_version: PlutusVersion,
#[serde(skip_serializing_if = "Option::is_none")]
pub compiler: Option<Compiler>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<String>,
}
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Compiler {
pub name: String,
pub version: String,
}
#[derive(Debug, PartialEq, Clone)]
pub enum LookupResult<'a, T> {
One(String, &'a T),
Many,
}
impl Blueprint {
pub fn new(
config: &Config,
modules: &CheckedModules,
generator: &mut CodeGenerator,
) -> Result<Self, Error> {
let preamble = config.into();
let mut definitions = Definitions::new();
let validators: Result<Vec<_>, Error> = modules
.validators()
.flat_map(|(validator, def)| {
Validator::from_checked_module(modules, generator, validator, def, &config.plutus)
.into_iter()
.map(|result| {
result.map(|mut schema| {
definitions.merge(&mut schema.definitions);
schema.definitions = Definitions::new();
schema
})
})
.collect::<Vec<_>>()
})
.collect();
Ok(Blueprint {
preamble,
validators: validators?,
definitions,
})
}
}
impl Blueprint {
pub fn lookup(
&self,
want_module_name: Option<&str>,
want_validator_name: Option<&str>,
) -> Option<LookupResult<Validator>> {
let mut validator = None;
for v in self.validators.iter() {
let (known_module_name, known_validator_name) = v.get_module_and_name();
let is_target = match (want_module_name, want_validator_name) {
(None, None) => true,
(Some(want_module_name), None) => want_module_name == known_module_name,
(None, Some(want_validator_name)) => want_validator_name == known_validator_name,
(Some(want_module_name), Some(want_validator_name)) => {
want_module_name == known_module_name
&& want_validator_name == known_validator_name
}
};
let title = format!("{known_module_name}.{known_validator_name}");
if is_target {
match validator {
Some(LookupResult::Many) => (),
None => {
validator = Some(LookupResult::One(title, v));
}
Some(LookupResult::One(ref known_title, _)) => {
if title.as_str() != known_title {
validator = Some(LookupResult::Many)
}
}
}
}
}
validator
}
pub fn with_validator<F, A, E>(
&self,
module_name: Option<&str>,
validator_name: Option<&str>,
when_too_many: fn(BTreeSet<(String, String, bool)>) -> E,
when_missing: fn(BTreeSet<(String, String, bool)>) -> E,
action: F,
) -> Result<A, E>
where
F: Fn(&Validator) -> Result<A, E>,
{
match self.lookup(module_name, validator_name) {
Some(LookupResult::One(_, validator)) => action(validator),
Some(LookupResult::Many) => Err(when_too_many(
self.validators
.iter()
.filter_map(|v| {
let (l, r) = v.get_module_and_name();
if let Some(module_name) = module_name {
if l != module_name {
return None;
}
}
if let Some(validator_name) = validator_name {
if r != validator_name {
return None;
}
}
Some((l.to_string(), r.to_string(), !v.parameters.is_empty()))
})
.collect(),
)),
None => Err(when_missing(
self.validators
.iter()
.map(|v| {
let (l, r) = v.get_module_and_name();
(l.to_string(), r.to_string(), !v.parameters.is_empty())
})
.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())
},
compiler: Some(Compiler {
name: "Aiken".to_string(),
version: config::compiler_version(true),
}),
plutus_version: config.plutus,
version: config.version.clone(),
license: config.license.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use aiken_lang::tipo::Type;
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,
compiler: Some(Compiler {
name: "Aiken".to_string(),
version: "1.0.0".to_string(),
}),
license: Some("Apache-2.0".to_string()),
},
validators: vec![],
definitions: Definitions::new(),
};
assert_eq!(
serde_json::to_value(&blueprint).unwrap(),
json!({
"preamble": {
"title": "Foo",
"version": "1.0.0",
"plutusVersion": "v2",
"compiler": {
"name": "Aiken",
"version": "1.0.0"
},
"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,
compiler: None,
license: None,
},
validators: vec![],
definitions: Definitions::new(),
};
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>(&Type::int(), &HashMap::new(), |_| {
Ok(Schema::Data(Data::Integer).into())
})
.unwrap();
definitions
.register::<_, Error>(
&Type::list(Type::byte_array()),
&HashMap::new(),
|definitions| {
let ref_bytes = definitions.register::<_, Error>(
&Type::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,
compiler: None,
license: None,
},
validators: vec![],
definitions,
};
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"
}
}
}
}),
);
}
}