use std::any::Any;
use std::fmt;
use std::sync::OnceLock;
pub use crate::fixture::ExecCtx;
pub use polydat::Kernel;
pub use polydat::kernel::interp::{KernelLookup, Lookup};
pub use polydat::ast::PortType;
pub fn verify_binders(binders: &[Binder], kernel: &dyn Kernel) -> Result<(), String> {
let violations = polydat::binder::verify_binders(binders, |name: &str| {
kernel
.output_type(name)
.or_else(|| kernel.input_port_type(name))
});
if violations.is_empty() {
Ok(())
} else {
Err(violations
.into_iter()
.map(|v| v.message)
.collect::<Vec<_>>()
.join("; "))
}
}
pub use polydat::binder::{Binder, BinderSlot};
pub type MapOpFuture<'a> = std::pin::Pin<
Box<dyn std::future::Future<Output = Result<Box<dyn OpDispenser>, String>> + Send + 'a>,
>;
pub type CreateAdapterFuture = std::pin::Pin<
Box<dyn std::future::Future<Output = Result<std::sync::Arc<dyn DriverAdapter>, String>> + Send>,
>;
pub trait ResultBody: Send + Sync + fmt::Debug {
fn to_json(&self) -> serde_json::Value;
fn as_any(&self) -> &dyn Any;
fn element_count(&self) -> u64 {
1
}
fn byte_count(&self) -> Option<u64> {
None
}
fn to_text(&self) -> String {
serde_json::to_string(&self.to_json()).unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct TextBody(pub String);
impl ResultBody for TextBody {
fn to_json(&self) -> serde_json::Value {
serde_json::Value::String(self.0.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn to_text(&self) -> String {
self.0.clone()
}
}
#[derive(Debug, Clone)]
pub struct JsonBody(pub serde_json::Value);
impl ResultBody for JsonBody {
fn to_json(&self) -> serde_json::Value {
self.0.clone()
}
fn as_any(&self) -> &dyn Any {
self
}
fn element_count(&self) -> u64 {
match &self.0 {
serde_json::Value::Array(arr) => arr.len() as u64,
_ => 1,
}
}
fn to_text(&self) -> String {
serde_json::to_string(&self.0).unwrap_or_default()
}
}
#[derive(Default)]
pub struct OpResult {
pub body: Option<Box<dyn ResultBody>>,
pub skipped: bool,
}
impl OpResult {
pub fn skipped() -> Self {
Self {
body: None,
skipped: true,
}
}
}
impl fmt::Debug for OpResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OpResult")
.field("body", &self.body.as_ref().map(|b| b.to_json()))
.finish()
}
}
#[derive(Debug)]
pub enum ExecutionError {
Op(AdapterError),
Adapter(AdapterError),
}
impl ExecutionError {
pub fn error(&self) -> &AdapterError {
match self {
ExecutionError::Op(e) | ExecutionError::Adapter(e) => e,
}
}
pub fn is_adapter_level(&self) -> bool {
matches!(self, ExecutionError::Adapter(_))
}
}
impl fmt::Display for ExecutionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExecutionError::Op(e) => write!(f, "[op] {e}"),
ExecutionError::Adapter(e) => write!(f, "[adapter] {e}"),
}
}
}
impl std::error::Error for ExecutionError {}
#[derive(Debug)]
pub struct AdapterError {
pub error_name: String,
pub message: String,
pub retryable: bool,
}
impl fmt::Display for AdapterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.error_name, self.message)
}
}
impl std::error::Error for AdapterError {}
pub trait DriverAdapter: Send + Sync + 'static {
fn name(&self) -> &str;
fn map_op<'a>(
&'a self,
template: &'a nmbrs_workload::model::ParsedOp,
parent: std::sync::Arc<dyn Kernel>,
) -> MapOpFuture<'a>;
fn default_status_metrics(&self) -> Vec<StatusMetric> {
Vec::new()
}
fn display_preference(&self) -> DisplayPreference {
DisplayPreference::Auto
}
fn known_op_fields(&self) -> Option<&'static [&'static str]> {
None
}
fn known_op_params(&self) -> &'static [&'static str] {
&[]
}
fn declare_controls(
&self,
_parent: &std::sync::Arc<std::sync::RwLock<nmbrs_metrics::component::Component>>,
) {
}
fn shutdown<'a>(
&'a self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
Box::pin(async {})
}
fn accessor_payload(&self) -> Option<std::sync::Arc<dyn Any + Send + Sync>> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DisplayPreference {
Off = 0,
Auto = 1,
}
pub struct StatusMetric {
pub metric_name: String,
pub display: String,
pub render: StatusRender,
}
pub enum StatusRender {
Rate,
Count,
Latency,
}
pub trait OpDispenser: Send + Sync {
fn execute<'a>(
&'a self,
cycle: u64,
ctx: &'a crate::fixture::ExecCtx<'a>,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = Result<OpResult, ExecutionError>> + Send + 'a>,
>;
fn describe(&self) -> Option<String> {
self.inner_dispenser().and_then(|inner| inner.describe())
}
fn describe_resolved(&self, wires: &dyn crate::wires::WireSource) -> Option<String> {
self.inner_dispenser()
.and_then(|inner| inner.describe_resolved(wires))
}
fn canonical_kernel(&self) -> Option<&std::sync::Arc<dyn Kernel>> {
self.inner_dispenser()
.and_then(|inner| inner.canonical_kernel())
}
fn adapter_metrics(
&self,
) -> Vec<(
String,
nmbrs_metrics::labels::Labels,
nmbrs_metrics::snapshot::MetricValue,
)> {
if let Some(inner) = self.inner_dispenser() {
inner.adapter_metrics()
} else {
Vec::new()
}
}
fn status_counters(&self) -> Vec<(&str, u64)> {
if let Some(inner) = self.inner_dispenser() {
inner.status_counters()
} else {
Vec::new()
}
}
fn rows_per_op(&self) -> usize {
self.inner_dispenser()
.map(|inner| inner.rows_per_op())
.unwrap_or(1)
}
fn inner_dispenser(&self) -> Option<&dyn OpDispenser> {
None
}
}
pub trait WrappingDispenser: OpDispenser {}
pub struct ResolvedFields {
pub names: Vec<String>,
pub values: Vec<polydat::ast::Value>,
strings: OnceLock<Vec<String>>,
}
impl fmt::Debug for ResolvedFields {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ResolvedFields")
.field("names", &self.names)
.field("values", &self.values)
.finish()
}
}
impl Clone for ResolvedFields {
fn clone(&self) -> Self {
Self {
names: self.names.clone(),
values: self.values.clone(),
strings: self.strings.clone(),
}
}
}
impl ResolvedFields {
pub fn new(names: Vec<String>, values: Vec<polydat::ast::Value>) -> Self {
Self {
names,
values,
strings: OnceLock::new(),
}
}
pub fn strings(&self) -> &[String] {
self.strings
.get_or_init(|| self.values.iter().map(|v| v.to_display_string()).collect())
}
pub fn get_str(&self, name: &str) -> Option<&str> {
self.names
.iter()
.position(|n| n == name)
.map(|i| self.strings()[i].as_str())
}
pub fn get_value(&self, name: &str) -> Option<&polydat::ast::Value> {
self.names
.iter()
.position(|n| n == name)
.map(|i| &self.values[i])
}
pub fn str_at(&self, index: usize) -> &str {
&self.strings()[index]
}
pub fn without(&self, name: &str) -> Self {
let mut names = Vec::new();
let mut values = Vec::new();
for (i, n) in self.names.iter().enumerate() {
if n != name {
names.push(n.clone());
values.push(self.values[i].clone());
}
}
Self::new(names, values)
}
pub fn to_json(&self) -> serde_json::Value {
let map: serde_json::Map<String, serde_json::Value> = self
.names
.iter()
.zip(self.values.iter())
.map(|(name, value)| {
let json_val = match value {
polydat::ast::Value::U64(v) => serde_json::Value::Number((*v).into()),
polydat::ast::Value::F64(v) => serde_json::Number::from_f64(*v)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
polydat::ast::Value::Bool(v) => serde_json::Value::Bool(*v),
_ => serde_json::Value::String(value.to_display_string()),
};
(name.clone(), json_val)
})
.collect();
serde_json::Value::Object(map)
}
}
#[derive(Debug, Clone)]
pub struct CaptureDecl {
pub source_name: String,
pub as_name: String,
pub type_qualifier: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolved_fields_lazy_strings() {
let fields = ResolvedFields::new(
vec!["a".into(), "b".into()],
vec![polydat::ast::Value::U64(42), polydat::ast::Value::F64(3.5)],
);
assert!(fields.strings.get().is_none());
assert_eq!(fields.get_str("a"), Some("42"));
assert!(fields.strings.get().is_some());
assert_eq!(fields.get_str("b"), Some("3.5"));
}
#[test]
fn resolved_fields_get_value() {
let fields = ResolvedFields::new(vec!["x".into()], vec![polydat::ast::Value::F64(3.5)]);
match fields.get_value("x") {
Some(polydat::ast::Value::F64(v)) => assert!((v - 3.5).abs() < 1e-10),
other => panic!("expected F64(3.5), got {other:?}"),
}
assert!(fields.strings.get().is_none());
}
#[test]
fn execution_error_display() {
let op_err = ExecutionError::Op(AdapterError {
error_name: "Timeout".into(),
message: "timed out".into(),
retryable: true,
});
assert!(format!("{op_err}").contains("[op]"));
assert!(!op_err.is_adapter_level());
let adapter_err = ExecutionError::Adapter(AdapterError {
error_name: "ConnectionRefused".into(),
message: "refused".into(),
retryable: false,
});
assert!(format!("{adapter_err}").contains("[adapter]"));
assert!(adapter_err.is_adapter_level());
}
}
pub struct AdapterRegistration {
pub names: fn() -> &'static [&'static str],
pub known_params: fn() -> &'static [&'static str],
pub display_preference: fn(&std::collections::HashMap<String, String>) -> DisplayPreference,
pub supported_controls: fn() -> &'static [crate::control_catalog::ControlDesc],
pub create: fn(std::collections::HashMap<String, String>) -> CreateAdapterFuture,
}
inventory::collect!(AdapterRegistration);
pub fn find_adapter_registration(driver: &str) -> Option<&'static AdapterRegistration> {
inventory::iter::<AdapterRegistration>
.into_iter()
.find(|®| (reg.names)().contains(&driver))
.map(|v| v as _)
}
pub fn registered_driver_names() -> Vec<&'static str> {
let mut names = Vec::new();
for reg in inventory::iter::<AdapterRegistration> {
names.extend_from_slice((reg.names)());
}
names
}
pub fn adapter_display_preference(
driver: &str,
params: &std::collections::HashMap<String, String>,
) -> DisplayPreference {
find_adapter_registration(driver)
.map(|reg| (reg.display_preference)(params))
.unwrap_or(DisplayPreference::Auto)
}
pub fn registered_adapter_params() -> Vec<&'static str> {
let mut params = Vec::new();
for reg in inventory::iter::<AdapterRegistration> {
params.extend_from_slice((reg.known_params)());
}
for entry in inventory::iter::<DriverImpl> {
params.extend_from_slice((entry.known_params)());
}
params
}
pub struct DriverImpl {
pub adapter: &'static str,
pub driver: &'static str,
pub default_rank: u32,
pub create: fn(std::collections::HashMap<String, String>) -> CreateAdapterFuture,
pub known_params: fn() -> &'static [&'static str],
}
inventory::collect!(DriverImpl);
pub struct SharedDriverRegistration {
pub adapter: &'static str,
pub driver: &'static str,
pub share_capability: crate::resource_pool::ShareCapability,
pub resource_key: fn(
&std::collections::HashMap<String, String>,
) -> Result<crate::resource_pool::ResourceKey, String>,
}
inventory::collect!(SharedDriverRegistration);
pub fn find_shared_driver(
adapter: &str,
driver: &str,
) -> Option<&'static SharedDriverRegistration> {
inventory::iter::<SharedDriverRegistration>
.into_iter()
.find(|e| e.adapter == adapter && e.driver == driver)
}
pub fn default_drivers(adapter: &str) -> Vec<&'static str> {
let mut entries: Vec<&'static DriverImpl> = inventory::iter::<DriverImpl>
.into_iter()
.filter(|e| e.adapter == adapter)
.collect();
entries.sort_by_key(|e| e.default_rank);
entries.into_iter().map(|e| e.driver).collect()
}
pub fn find_driver(adapter: &str, driver: &str) -> Option<&'static DriverImpl> {
inventory::iter::<DriverImpl>
.into_iter()
.find(|e| e.adapter == adapter && e.driver == driver)
}
pub fn adapter_driver_params(adapter: &str) -> Vec<&'static str> {
let mut params: Vec<&'static str> = Vec::new();
for e in inventory::iter::<DriverImpl>
.into_iter()
.filter(|e| e.adapter == adapter)
{
params.extend_from_slice((e.known_params)());
}
params
}
pub const DEFAULT_DRIVER_NAME: &str = "default";
pub fn resolve_driver_name(
adapter: &str,
selector_param: &str,
params: &std::collections::HashMap<String, String>,
) -> Option<&'static str> {
let user_order: Option<Vec<&str>> = params.get(selector_param).map(|s| {
s.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect()
});
let default_order = default_drivers(adapter);
let order: Vec<&str> = match &user_order {
Some(v) => v.clone(),
None => default_order.to_vec(),
};
for driver in &order {
if let Some(entry) = find_driver(adapter, driver) {
return Some(entry.driver);
}
}
Some(DEFAULT_DRIVER_NAME)
}
pub async fn instantiate_with_driver(
adapter: &str,
selector_param: &str,
params: std::collections::HashMap<String, String>,
) -> Result<std::sync::Arc<dyn DriverAdapter>, String> {
let user_order: Option<Vec<&str>> = params.get(selector_param).map(|s| {
s.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect()
});
let default_order = default_drivers(adapter);
let order: Vec<&str> = match &user_order {
Some(v) => v.clone(),
None => default_order.clone(),
};
if order.is_empty() {
return Err(format!(
"adapter '{adapter}': no driver implementations registered. \
Build the binary with at least one driver feature enabled."
));
}
for driver in &order {
if let Some(entry) = find_driver(adapter, driver) {
return (entry.create)(params).await;
}
}
Err(format!(
"adapter '{adapter}': no driver in {selector_param}='{}' is compiled in; \
available drivers: [{}]",
order.join(","),
default_order.join(", "),
))
}