#![allow(
unsafe_code,
unused_qualifications,
reason = "generated by Starlark's derive macros, not written here"
)]
pub mod fleet;
mod globals;
mod handle;
mod refuse;
pub mod template;
use std::cell::RefCell;
use nerpa_core::{Address, Graph, Resource, Target};
use starlark::environment::{GlobalsBuilder, Module};
use starlark::eval::Evaluator;
use starlark::syntax::{AstModule, Dialect};
#[derive(Debug, thiserror::Error)]
#[allow(
clippy::large_enum_variant,
reason = "diagnostics beat a lint on a cold path"
)]
#[non_exhaustive]
pub enum ConfigError {
#[error("{0}")]
Starlark(String),
#[error("{0}")]
Declaration(String),
#[error("{0}")]
ComparedUnresolved(String),
#[error(
"a value that will not exist until apply was used as a condition: {0}. \
Which resources exist cannot depend on something that has not happened yet"
)]
BranchedOnUnresolved(String),
#[error("this configuration requires nerpa {0}, and this build is {1}")]
VersionMismatch(String, String),
#[error(transparent)]
Graph(#[from] nerpa_core::GraphError),
}
#[derive(Debug, starlark::any::ProvidesStaticType)]
pub(crate) struct Declarations {
fleet: crate::fleet::Fleet,
renderer: crate::template::Renderer,
resources: RefCell<Vec<Resource>>,
nodes: RefCell<Vec<(Target, Option<Address>)>>,
moves: RefCell<Vec<(Address, Address)>>,
problems: RefCell<Vec<String>>,
required_version: RefCell<Option<String>>,
schema: nerpa_core::Schema,
}
impl Declarations {
fn with(
renderer: crate::template::Renderer,
fleet: crate::fleet::Fleet,
schema: nerpa_core::Schema,
) -> Self {
Self {
fleet,
renderer,
resources: RefCell::new(Vec::new()),
nodes: RefCell::new(Vec::new()),
moves: RefCell::new(Vec::new()),
problems: RefCell::new(Vec::new()),
required_version: RefCell::new(None),
schema,
}
}
pub(crate) fn schema(&self) -> &nerpa_core::Schema {
&self.schema
}
pub(crate) fn renderer(&self) -> &crate::template::Renderer {
&self.renderer
}
pub(crate) fn fleet(&self) -> &crate::fleet::Fleet {
&self.fleet
}
pub(crate) fn add_resource(&self, resource: Resource) {
self.resources.borrow_mut().push(resource);
}
pub(crate) fn add_node(&self, target: Target, provided_by: Option<Address>) {
self.nodes.borrow_mut().push((target, provided_by));
}
pub(crate) fn add_moved(&self, from: Address, to: Address) {
self.moves.borrow_mut().push((from, to));
}
pub(crate) fn add_problem(&self, problem: String) {
self.problems.borrow_mut().push(problem);
}
pub(crate) fn require_version(&self, version: &str) {
self.required_version
.borrow_mut()
.replace(version.to_owned());
}
pub(crate) fn required_version(&self) -> Option<String> {
self.required_version.borrow().clone()
}
}
#[allow(
clippy::result_large_err,
reason = "diagnostics beat a lint on a cold path"
)]
pub fn evaluate(
path: &str,
source: &str,
templates: Box<dyn crate::template::Templates>,
fleet: crate::fleet::Fleet,
) -> Result<Graph, ConfigError> {
evaluate_with(path, source, templates, fleet, nerpa_core::Schema::new())
}
#[allow(
clippy::result_large_err,
reason = "diagnostics beat a lint on a cold path"
)]
pub fn evaluate_with(
path: &str,
source: &str,
templates: Box<dyn crate::template::Templates>,
fleet: crate::fleet::Fleet,
schema: nerpa_core::Schema,
) -> Result<Graph, ConfigError> {
let dialect = Dialect {
enable_top_level_stmt: true,
..Dialect::Standard
};
let ast = AstModule::parse(path, source.to_owned(), &dialect)
.map_err(|error| ConfigError::Starlark(format!("{error:?}")))?;
if let Some(refusal) = refuse::comparisons(&ast).into_iter().next() {
return Err(ConfigError::ComparedUnresolved(refusal));
}
let globals = GlobalsBuilder::standard().with(globals::nerpa).build();
let declarations = Declarations::with(crate::template::Renderer::new(templates), fleet, schema);
handle::forget_branches();
Module::with_temp_heap(|module| {
let mut evaluator = Evaluator::new(&module);
evaluator.extra = Some(&declarations);
evaluator.eval_module(ast, &globals)?;
starlark::Result::Ok(())
})
.map_err(|error| ConfigError::Starlark(format!("{error:?}")))?;
if let Some(reference) = handle::branches_taken().first() {
return Err(ConfigError::BranchedOnUnresolved(reference.clone()));
}
if let Some(problem) = declarations.problems.borrow().first() {
return Err(ConfigError::Declaration(problem.clone()));
}
if let Some(required) = declarations.required_version() {
let current = env!("CARGO_PKG_VERSION");
if required != current {
return Err(ConfigError::VersionMismatch(required, current.to_owned()));
}
}
let mut graph = Graph::new();
for resource in declarations.resources.take() {
graph.insert(resource)?;
}
for (target, provided_by) in declarations.nodes.take() {
match provided_by {
Some(address) => graph.managed_by(target, address),
None => graph.preexisting(target),
}
}
for (from, to) in declarations.moves.take() {
graph = graph.moved(from, to);
}
Ok(graph)
}