use super::builtins::BuiltinKind;
use super::erased::{Erased, ErasedFunction};
use super::templated::{default_problem, primary_problem};
use super::{CallLayer, Function, FunctionConfig, FunctionSpec};
use crate::base::{AuthoredPath, Error, ErrorKind, PathSeg};
use crate::expr::{ExprEngine, FieldKind, FieldSpec, ValueKind};
use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Clone)]
pub struct Registry {
inner: Arc<Inner>,
}
#[derive(Clone)]
struct Inner {
exprs: ExprEngine,
builtin_specs: [FunctionSpec; BuiltinKind::ALL.len()],
hosts: BTreeMap<Box<str>, Arc<dyn ErasedFunction>>,
layers: Vec<Arc<dyn CallLayer>>,
}
#[derive(Clone, Copy)]
pub(crate) enum Entry<'r> {
Builtin(BuiltinKind),
Host(&'r dyn ErasedFunction),
}
impl Registry {
pub fn new(exprs: ExprEngine) -> Self {
Self {
inner: Arc::new(Inner {
exprs,
builtin_specs: BuiltinKind::ALL.map(BuiltinKind::spec),
hosts: BTreeMap::new(),
layers: Vec::new(),
}),
}
}
pub fn register<F: Function>(&mut self, name: &str, f: F) {
if let Err(error) = self.try_register(name, f) {
panic!("{}", error.message());
}
}
pub fn try_register<F: Function>(&mut self, name: &str, f: F) -> Result<(), Error> {
if crate::def::is_reserved_function(name) {
return Err(Error::new(
ErrorKind::Config,
format!(
"cannot register `{name}`: the name is reserved for the built-in functions \
(def::BUILTIN_FUNCTIONS, and v3's alias for `validation`)"
),
));
}
let erased = Erased::new(f);
let templated = <F::Config as FunctionConfig>::TEMPLATED;
check_spec(
name,
erased.spec(),
erased.fields(),
templated,
&self.inner.exprs,
)
.map_err(|message| Error::new(ErrorKind::Config, message))?;
let erased: Arc<dyn ErasedFunction> = Arc::new(erased);
Arc::make_mut(&mut self.inner)
.hosts
.insert(Box::from(name), erased);
Ok(())
}
pub fn layer<L: CallLayer>(&mut self, layer: L) {
Arc::make_mut(&mut self.inner).layers.push(Arc::new(layer));
}
pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
pub fn exprs(&self) -> &ExprEngine {
&self.inner.exprs
}
pub fn catalogue(&self) -> impl Iterator<Item = FunctionInfo<'_>> {
let builtins = BuiltinKind::ALL
.into_iter()
.zip(&self.inner.builtin_specs)
.map(|(kind, spec)| FunctionInfo {
name: kind.name(),
builtin: true,
spec,
fields: kind.fields(),
});
let hosts = self.inner.hosts.iter().map(|(name, f)| FunctionInfo {
name,
builtin: false,
spec: f.spec(),
fields: f.fields(),
});
builtins.chain(hosts)
}
pub(crate) fn entry(&self, name: &str) -> Option<Entry<'_>> {
match BuiltinKind::from_name(name) {
Some(kind) => Some(Entry::Builtin(kind)),
None => self.inner.hosts.get(name).map(|f| Entry::Host(&**f)),
}
}
pub(crate) fn layers(&self) -> &[Arc<dyn CallLayer>] {
&self.inner.layers
}
}
fn check_spec(
name: &str,
spec: &FunctionSpec,
fields: &[FieldSpec],
templated: bool,
exprs: &ExprEngine,
) -> Result<(), String> {
match (templated, spec.runtime_fields.is_some()) {
(false, true) => {
return Err(format!(
"cannot register `{name}`: its spec declares runtime fields, but its config is \
not `Templated`, so it never compiles them"
));
}
(true, false) => {
return Err(format!(
"cannot register `{name}`: its config is `Templated`, but its spec declares no \
runtime fields, so every compile of it fails"
));
}
_ => {}
}
if let Some(problem) = spec.runtime_fields.as_deref().and_then(primary_problem) {
return Err(format!("cannot register `{name}`: {problem}"));
}
for (index, field) in fields.iter().enumerate() {
if fields[..index].iter().any(|other| other.name == field.name) {
return Err(format!(
"cannot register `{name}`: its field `{}` is declared twice",
field.name
));
}
if templated
&& matches!(
field.kind,
FieldKind::Nested(_)
| FieldKind::List(_)
| FieldKind::Map(_)
| FieldKind::Templated
)
{
return Err(format!(
"cannot register `{name}`: its runtime field `{}` has an unsupported kind; \
use an expression, document, literal or output",
field.name
));
}
if let Some(problem) = default_problem(exprs, field) {
return Err(format!("cannot register `{name}`: {problem}"));
}
}
for reference in &spec.references {
let field = &reference.field;
let path = AuthoredPath::from_str(field).map_err(|error| {
format!(
"cannot register `{name}`: its reference field `{field}` is not an authored \
path ({error})"
)
})?;
if let Err(why) = reaches(fields, path.segs()) {
return Err(format!(
"cannot register `{name}`: its reference field `{field}` {why}"
));
}
}
Ok(())
}
fn reaches(fields: &[FieldSpec], segs: &[PathSeg]) -> Result<(), String> {
let start = || "does not start at a field of its config".to_owned();
let Some((PathSeg::Key(first), rest)) = segs.split_first() else {
return Err(start());
};
let Some(spec) = fields.iter().find(|f| f.name == **first) else {
return Err(start());
};
let mut kind = spec.kind;
let mut at = AuthoredPath::root().join(&**first);
for seg in rest {
let refused = |why: String| {
Err(format!(
"does not reach a field of its config: `{at}` {why}"
))
};
kind = match (kind, seg) {
(
FieldKind::Literal(ValueKind::Any | ValueKind::Object | ValueKind::Array)
| FieldKind::Interpolated
| FieldKind::Templated,
_,
) => return Ok(()),
(FieldKind::Nested(fields), PathSeg::Key(key)) => {
match fields().iter().find(|f| f.name == **key) {
Some(spec) => spec.kind,
None => return refused(format!("has no field `{key}`")),
}
}
(FieldKind::List(element), PathSeg::Index(_)) => element(),
(FieldKind::Map(element), PathSeg::Key(_)) => element(),
(FieldKind::List(_), PathSeg::Key(key)) => {
return refused(format!("is a list, so `{key}` is not an index"));
}
(_, PathSeg::Index(index)) => {
return refused(format!("is not a list, so it has no `[{index}]`"));
}
(_, PathSeg::Key(key)) => {
return refused(format!("holds no fields, so it has no `{key}`"));
}
};
at.push(seg.clone());
}
Ok(())
}
impl Default for Registry {
fn default() -> Self {
Self::new(ExprEngine::default())
}
}
impl fmt::Debug for Registry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names: Vec<&str> = self.catalogue().map(|info| info.name).collect();
f.debug_struct("Registry")
.field("functions", &names)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct FunctionInfo<'a> {
pub name: &'a str,
pub builtin: bool,
pub spec: &'a FunctionSpec,
pub fields: &'a [FieldSpec],
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::Result;
use crate::def::BUILTIN_FUNCTIONS;
use crate::function::{BoxFuture, Ctx, Next, Outcome};
struct Ping;
impl Function for Ping {
type Config = ();
fn spec(&self) -> FunctionSpec {
FunctionSpec::default().with_description("ping")
}
async fn call(&self, _ctx: &mut Ctx<'_>, _cfg: &()) -> Result<Outcome> {
Ok(Outcome::Ok)
}
}
struct Layer;
impl CallLayer for Layer {
fn call<'a>(
&'a self,
ctx: &'a mut Ctx<'_>,
next: Next<'a>,
) -> BoxFuture<'a, Result<Outcome>> {
next.run(ctx)
}
}
#[test]
fn a_builtin_name_resolves_to_its_kind() {
let registry = Registry::default();
for name in BUILTIN_FUNCTIONS {
match registry.entry(name) {
Some(Entry::Builtin(kind)) => assert_eq!(kind.name(), name),
_ => panic!("`{name}` is a built-in"),
}
}
assert!(registry.entry("validate").is_none());
assert!(registry.entry("ping").is_none());
}
#[test]
fn a_host_name_resolves_to_its_function() {
let mut registry = Registry::default();
registry.register("ping", Ping);
match registry.entry("ping") {
Some(Entry::Host(f)) => assert_eq!(f.spec().description, "ping"),
_ => panic!("`ping` is registered"),
}
}
#[test]
fn the_builtin_specs_are_computed_in_catalogue_order() {
let registry = Registry::default();
for (kind, info) in BuiltinKind::ALL.into_iter().zip(registry.catalogue()) {
assert_eq!(info.name, kind.name());
assert_eq!(info.spec.description, kind.spec().description);
assert_eq!(info.spec.category, kind.spec().category);
}
}
#[test]
fn a_clone_shares_the_table_until_it_is_written() {
let mut registry = Registry::default();
registry.layer(Layer);
let mut clone = registry.clone();
assert!(Arc::ptr_eq(&clone.inner, ®istry.inner));
clone.layer(Layer);
assert!(!Arc::ptr_eq(&clone.inner, ®istry.inner));
assert_eq!(registry.layers().len(), 1);
assert_eq!(clone.layers().len(), 2);
}
}