#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
mod bridge;
mod components;
mod desktop;
mod html;
mod worker;
pub mod cookbook;
pub use components::{Choice, Input, Output, Value};
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Configuration(String),
Input {
field: Option<usize>,
message: String,
},
Prediction(String),
Output(String),
Desktop(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Configuration(message) => write!(f, "Invalid configuration: {message}"),
Self::Input { message, .. } => f.write_str(message),
Self::Prediction(message) => write!(f, "Prediction failed: {message}"),
Self::Output(message) => write!(f, "Invalid prediction output: {message}"),
Self::Desktop(message) => write!(f, "Desktop error: {message}"),
}
}
}
impl std::error::Error for Error {}
type Predictor = dyn Fn(&[Value]) -> Result<Vec<Value>, String>;
type Factory = dyn FnOnce() -> Result<Box<Predictor>, String> + Send;
pub struct Teaser {
title: String,
description: String,
inputs: Vec<Input>,
outputs: Vec<Output>,
factory: Box<Factory>,
numeric_callback: bool,
}
impl Default for Teaser {
fn default() -> Self {
Self {
title: "Demo".into(),
description: String::new(),
inputs: vec![Input::default()],
outputs: vec![Output::default()],
factory: Box::new(|| Ok(Box::new(|values| Ok(vec![values[0].clone()])))),
numeric_callback: true,
}
}
}
impl Teaser {
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
self.inputs = inputs;
self
}
pub fn with_output(self, output: Output) -> Self {
self.with_outputs(vec![output])
}
pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
self.outputs = outputs;
self
}
pub fn with_function(mut self, function: impl Fn(Vec<f64>) -> f64 + Send + 'static) -> Self {
self.factory = Box::new(move || {
Ok(Box::new(move |values: &[Value]| {
let numbers = values
.iter()
.map(Value::as_number)
.collect::<Result<_, _>>()?;
Ok(vec![Value::Number(function(numbers))])
}))
});
self.numeric_callback = true;
self
}
pub fn with_predictor(
mut self,
predictor: impl Fn(&[Value]) -> Result<Vec<Value>, String> + Send + 'static,
) -> Self {
self.factory = Box::new(move || Ok(Box::new(predictor)));
self.numeric_callback = false;
self
}
pub fn with_predictor_factory<F, P>(mut self, factory: F) -> Self
where
F: FnOnce() -> Result<P, String> + Send + 'static,
P: Fn(&[Value]) -> Result<Vec<Value>, String> + 'static,
{
self.factory =
Box::new(move || factory().map(|predictor| Box::new(predictor) as Box<Predictor>));
self.numeric_callback = false;
self
}
pub fn validate(&self) -> Result<(), Error> {
if self.inputs.is_empty() || self.outputs.is_empty() {
return Err(Error::Configuration(
"At least one input and output are required.".into(),
));
}
for (index, input) in self.inputs.iter().enumerate() {
input.validate(index)?;
}
for output in &self.outputs {
if let Output::Number { precision, .. } = output {
if *precision > 15 {
return Err(Error::Configuration(
"Output precision must be at most 15.".into(),
));
}
}
}
if self.numeric_callback
&& (self.inputs.iter().any(|input| !input.is_numeric())
|| !matches!(self.outputs.as_slice(), [Output::Number { .. }]))
{
return Err(Error::Configuration(
"with_function requires numerical inputs and one numerical output; use with_predictor for typed results.".into(),
));
}
Ok(())
}
pub fn prepare(self) -> Result<Session, Error> {
self.validate()?;
let predictor = (self.factory)().map_err(Error::Prediction)?;
Ok(Session {
inputs: self.inputs,
outputs: self.outputs,
predictor,
})
}
pub fn run(self) -> Result<(), Error> {
self.validate()?;
desktop::run(self)
}
}
pub struct Session {
inputs: Vec<Input>,
outputs: Vec<Output>,
predictor: Box<Predictor>,
}
impl Session {
pub fn predict(&self, values: &[Value]) -> Result<Vec<Value>, Error> {
if values.len() != self.inputs.len() {
return Err(Error::Input {
field: None,
message: format!(
"Expected {} inputs, received {}.",
self.inputs.len(),
values.len()
),
});
}
for (index, (input, value)) in self.inputs.iter().zip(values).enumerate() {
input.validate_value(index, value)?;
}
let results = (self.predictor)(values).map_err(Error::Prediction)?;
if results.len() != self.outputs.len() {
return Err(Error::Output(format!(
"Expected {} results, received {}.",
self.outputs.len(),
results.len()
)));
}
for (index, (output, value)) in self.outputs.iter().zip(&results).enumerate() {
match (output, value) {
(Output::Number { .. }, Value::Number(number)) if number.is_finite() => {}
(Output::Text { .. }, Value::Text(_)) => {}
_ => {
return Err(Error::Output(format!(
"Result {} has the wrong type or is not finite.",
index + 1
)))
}
}
}
Ok(results)
}
}