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

284 lines
8.3 KiB
Rust

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<Validator>,
#[serde(skip_serializing_if = "Definitions::is_empty", default)]
pub definitions: Definitions<Annotated<Schema>>,
pub functions: Vec<Function>,
}
#[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 license: Option<String>,
}
#[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<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)
.into_iter()
.map(|result| {
result.map(|mut schema| {
definitions.merge(&mut schema.definitions);
schema.definitions = Definitions::new();
schema
})
})
.collect::<Vec<_>>()
})
.collect();
let functions: Result<Vec<_>, 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<LookupResult<Validator>> {
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<F, A, E>(
&self,
title: Option<&String>,
when_too_many: fn(Vec<String>) -> E,
when_missing: fn(Vec<String>) -> E,
action: F,
) -> Result<A, E>
where
F: Fn(Validator) -> Result<A, E>,
{
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"
}
}
}
}),
);
}
}