use serde_json::Value;
use std::sync::Arc;
use super::action::{resolve_action, ActionResult};
use super::graph::WorkflowGraph;
#[derive(Debug, Clone)]
struct ActionContext {
graph: Arc<WorkflowGraph>,
source_uid: String,
}
#[derive(Debug, Clone)]
pub struct Screen {
items: Vec<ScreenItem>,
raw: Value,
context: Option<ActionContext>,
}
impl Screen {
pub fn from_json(json: &str) -> Result<Self, ScreenError> {
let raw: Value = serde_json::from_str(json).map_err(ScreenError::InvalidJson)?;
let items_array = raw
.get("items")
.and_then(Value::as_array)
.ok_or(ScreenError::MissingItems)?;
let items = items_array.iter().map(|v| ScreenItem(v.clone())).collect();
Ok(Self {
items,
raw,
context: None,
})
}
pub fn with_context(mut self, graph: Arc<WorkflowGraph>, source_uid: String) -> Self {
self.context = Some(ActionContext { graph, source_uid });
self
}
pub fn items(&self) -> &[ScreenItem] {
&self.items
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn raw_json(&self) -> &Value {
&self.raw
}
pub fn assert_renders(&self) {
assert!(
!self.items.is_empty(),
"Expected screen to render items, but it was empty"
);
}
pub fn action(&self, index: usize) -> Option<ActionResult> {
self.action_with_modifiers(index, 0)
}
pub fn action_first(&self) -> Option<ActionResult> {
if self.items.is_empty() {
return None;
}
self.action(0)
}
pub fn action_with_modifiers(&self, index: usize, modifiers: u64) -> Option<ActionResult> {
let ctx = self.context.as_ref()?;
let item = &self.items[index];
let item_context = super::action::ItemContext {
arg: item.arg(),
variables: item.raw().get("variables"),
};
Some(resolve_action(
&ctx.graph,
&ctx.source_uid,
item.is_valid(),
item.autocomplete(),
&item_context,
modifiers,
))
}
}
#[derive(Debug, Clone)]
pub struct ScreenItem(Value);
impl ScreenItem {
pub fn title(&self) -> &str {
self.0.get("title").and_then(Value::as_str).unwrap_or("")
}
pub fn subtitle(&self) -> Option<&str> {
self.0.get("subtitle").and_then(Value::as_str)
}
pub fn arg(&self) -> Option<&str> {
self.0.get("arg").and_then(Value::as_str)
}
pub fn args(&self) -> Vec<&str> {
match self.0.get("arg") {
Some(Value::String(s)) => vec![s.as_str()],
Some(Value::Array(arr)) => arr.iter().filter_map(Value::as_str).collect(),
_ => vec![],
}
}
pub fn is_valid(&self) -> bool {
self.0.get("valid").and_then(Value::as_bool).unwrap_or(true)
}
pub fn autocomplete(&self) -> Option<&str> {
self.0.get("autocomplete").and_then(Value::as_str)
}
pub fn uid(&self) -> Option<&str> {
self.0.get("uid").and_then(Value::as_str)
}
pub fn variable(&self, key: &str) -> Option<&str> {
self.0
.get("variables")
.and_then(|v| v.get(key))
.and_then(Value::as_str)
}
pub fn raw(&self) -> &Value {
&self.0
}
}
#[derive(Debug, thiserror::Error)]
pub enum ScreenError {
#[error("invalid JSON output: {0}")]
InvalidJson(serde_json::Error),
#[error("response JSON missing 'items' array")]
MissingItems,
}