use std::sync::Arc;
use ciborium::Value as CborValue;
use rhai::{Dynamic, Engine, EvalAltResult, Map as RhaiMap};
use vantage_types::Record;
use crate::servo::{Servo, ServoStatus};
pub fn register_servo_onto(engine: &mut Engine) {
engine.register_type_with_name::<Arc<Servo>>("Servo");
engine.register_fn("get", |servo: &mut Arc<Servo>, field: &str| -> Dynamic {
servo
.get(field)
.map(|v| cbor_to_dynamic(&v))
.unwrap_or(Dynamic::UNIT)
});
engine.register_fn(
"set",
|servo: &mut Arc<Servo>, field: &str, value: Dynamic| -> Result<(), Box<EvalAltResult>> {
servo.set(field, dynamic_to_cbor(&value)?);
Ok(())
},
);
engine.register_fn("id", |servo: &mut Arc<Servo>| -> Dynamic {
servo.id().map(Dynamic::from).unwrap_or(Dynamic::UNIT)
});
engine.register_fn("record", |servo: &mut Arc<Servo>| -> RhaiMap {
record_to_map(&servo.record())
});
engine.register_fn("baseline", |servo: &mut Arc<Servo>| -> Dynamic {
servo
.baseline()
.map(|r| Dynamic::from_map(record_to_map(&r)))
.unwrap_or(Dynamic::UNIT)
});
engine.register_fn("error", |servo: &mut Arc<Servo>| -> RhaiMap {
record_to_map(&servo.error())
});
engine.register_fn("dirty", |servo: &mut Arc<Servo>, field: &str| -> bool {
servo.dirty(field)
});
engine.register_fn("is_dirty", |servo: &mut Arc<Servo>| -> bool {
servo.is_dirty()
});
engine.register_fn("revert", |servo: &mut Arc<Servo>, field: &str| {
servo.revert(field);
});
engine.register_fn("revert_all", |servo: &mut Arc<Servo>| {
servo.revert_all();
});
engine.register_fn("status", |servo: &mut Arc<Servo>| -> &'static str {
match servo.status() {
ServoStatus::Tracking => "tracking",
ServoStatus::Pending => "pending",
ServoStatus::Failed(_) => "failed",
}
});
engine.register_fn("rejection", |servo: &mut Arc<Servo>| -> Dynamic {
match servo.status() {
ServoStatus::Failed(rejection) => {
let mut fields = RhaiMap::new();
for (field, message) in rejection.field_errors() {
fields.insert(field.as_str().into(), Dynamic::from(message.clone()));
}
let mut map = RhaiMap::new();
map.insert(
"message".into(),
Dynamic::from(rejection.message().to_string()),
);
map.insert("fields".into(), Dynamic::from_map(fields));
Dynamic::from_map(map)
}
_ => Dynamic::UNIT,
}
});
engine.register_fn(
"save",
|servo: &mut Arc<Servo>| -> Result<Dynamic, Box<EvalAltResult>> {
let handle = tokio::runtime::Handle::try_current().map_err(|_| {
Box::<EvalAltResult>::from(
"servo save() needs a tokio runtime context (run the script via spawn_blocking)",
)
})?;
handle
.block_on(servo.flash())
.map_err(|e| Box::<EvalAltResult>::from(format!("save failed: {e}")))?;
Ok(servo.id().map(Dynamic::from).unwrap_or(Dynamic::UNIT))
},
);
}
fn record_to_map(record: &Record<CborValue>) -> RhaiMap {
let mut map = RhaiMap::new();
for (key, value) in record.iter() {
map.insert(key.as_str().into(), cbor_to_dynamic(value));
}
map
}
pub fn cbor_to_dynamic(value: &CborValue) -> Dynamic {
match value {
CborValue::Null => Dynamic::UNIT,
CborValue::Bool(b) => Dynamic::from(*b),
CborValue::Integer(i) => {
let wide: i128 = (*i).into();
i64::try_from(wide)
.map(Dynamic::from)
.unwrap_or_else(|_| Dynamic::from(wide as f64))
}
CborValue::Float(f) => Dynamic::from(*f),
CborValue::Text(s) => Dynamic::from(s.clone()),
CborValue::Bytes(b) => Dynamic::from_blob(b.clone()),
CborValue::Array(items) => Dynamic::from_array(items.iter().map(cbor_to_dynamic).collect()),
CborValue::Map(pairs) => {
let mut map = RhaiMap::new();
for (k, v) in pairs {
if let CborValue::Text(key) = k {
map.insert(key.as_str().into(), cbor_to_dynamic(v));
}
}
Dynamic::from_map(map)
}
CborValue::Tag(_, inner) => cbor_to_dynamic(inner),
_ => Dynamic::UNIT,
}
}
pub fn dynamic_to_cbor(value: &Dynamic) -> Result<CborValue, Box<EvalAltResult>> {
if value.is_unit() {
return Ok(CborValue::Null);
}
if let Some(b) = value.clone().try_cast::<bool>() {
return Ok(CborValue::Bool(b));
}
if let Some(i) = value.clone().try_cast::<i64>() {
return Ok(CborValue::Integer(i.into()));
}
if let Some(f) = value.clone().try_cast::<f64>() {
return Ok(CborValue::Float(f));
}
if let Some(s) = value.clone().try_cast::<String>() {
return Ok(CborValue::Text(s));
}
if let Some(blob) = value.clone().try_cast::<rhai::Blob>() {
return Ok(CborValue::Bytes(blob));
}
if let Some(dt) = value.clone().try_cast::<chrono::DateTime<chrono::Utc>>() {
return Ok(CborValue::Tag(
0,
Box::new(CborValue::Text(dt.to_rfc3339())),
));
}
if let Some(array) = value.clone().try_cast::<rhai::Array>() {
let items = array
.iter()
.map(dynamic_to_cbor)
.collect::<Result<Vec<_>, _>>()?;
return Ok(CborValue::Array(items));
}
if let Some(map) = value.clone().try_cast::<RhaiMap>() {
let pairs = map
.iter()
.map(|(k, v)| Ok((CborValue::Text(k.to_string()), dynamic_to_cbor(v)?)))
.collect::<Result<Vec<_>, Box<EvalAltResult>>>()?;
return Ok(CborValue::Map(pairs));
}
Err(format!(
"no CBOR representation for rhai type '{}'",
value.type_name()
)
.into())
}