use std::sync::Arc;
use ciborium::Value as CborValue;
use vantage_rhai::Vocab;
use vantage_rhai::rhai::{Dynamic, Engine, EvalAltResult, Map as RhaiMap};
pub use vantage_vista::{cbor_to_dynamic, record_to_map};
use crate::servo::{Servo, ServoStatus};
pub struct ServoVocab;
impl Vocab for ServoVocab {
fn register(&self, engine: &mut Engine) {
register_servo_onto(engine);
}
}
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))
},
);
}
pub fn dynamic_to_cbor(value: &Dynamic) -> Result<CborValue, Box<EvalAltResult>> {
if let Some(dt) = value.clone().try_cast::<chrono::DateTime<chrono::Utc>>() {
return Ok(CborValue::Tag(
0,
Box::new(CborValue::Text(dt.to_rfc3339())),
));
}
vantage_vista::dynamic_to_cbor(value.clone())
}