use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Cause {
NoRows,
Unreachable,
NotConfigured,
}
impl Cause {
pub fn label(self) -> &'static str {
match self {
Cause::NoRows => "no rows",
Cause::Unreachable => "unreachable",
Cause::NotConfigured => "not configured",
}
}
pub fn tag(self) -> &'static str {
match self {
Cause::NoRows => "no-rows",
Cause::Unreachable => "unreachable",
Cause::NotConfigured => "not-configured",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EmptyState {
pub feed: String,
pub cause: Cause,
pub detail: String,
pub hint: String,
}
impl EmptyState {
pub fn new(feed: impl Into<String>, cause: Cause, detail: impl Into<String>) -> Self {
Self { feed: feed.into(), cause, detail: detail.into(), hint: String::new() }
}
pub fn no_rows(feed: impl Into<String>, detail: impl Into<String>) -> Self {
Self::new(feed, Cause::NoRows, detail)
}
pub fn unreachable(feed: impl Into<String>, detail: impl Into<String>) -> Self {
Self::new(feed, Cause::Unreachable, detail)
}
pub fn not_configured(feed: impl Into<String>, detail: impl Into<String>) -> Self {
Self::new(feed, Cause::NotConfigured, detail)
}
pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
self.hint = hint.into();
self
}
pub fn ui(&self, ui: &mut egui::Ui) {
ui.add_space(6.0);
ui.horizontal(|ui| {
ui.weak("⌀");
ui.label(self.cause.label());
});
ui.horizontal_wrapped(|ui| {
ui.monospace(&self.feed);
if !self.detail.is_empty() {
ui.weak("—");
ui.weak(&self.detail);
}
});
if !self.hint.is_empty() {
ui.weak(format!("→ {}", self.hint));
}
ui.add_space(6.0);
}
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"empty": true,
"feed": self.feed,
"cause": self.cause.tag(),
"detail": self.detail,
"hint": self.hint,
})
}
pub fn line(&self) -> String {
let mut s = format!("{} — {} ({})", self.feed, self.cause.label(), self.detail);
if !self.hint.is_empty() {
s.push_str(&format!(" → {}", self.hint));
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_three_causes_are_distinct_on_the_wire() {
let tags: Vec<&str> =
[Cause::NoRows, Cause::Unreachable, Cause::NotConfigured].iter().map(|c| c.tag()).collect();
assert_eq!(tags, vec!["no-rows", "unreachable", "not-configured"]);
let mut sorted = tags.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), 3, "three causes, three tags");
}
#[test]
fn state_json_names_the_feed_and_flags_empty() {
let e = EmptyState::unreachable("dwarf.v1/Dwarf.History", "http://127.0.0.1:7900 refused")
.with_hint("start dwarves-server");
let j = e.state_json();
assert_eq!(j["empty"], true);
assert_eq!(j["feed"], "dwarf.v1/Dwarf.History");
assert_eq!(j["cause"], "unreachable");
assert_eq!(j["detail"], "http://127.0.0.1:7900 refused");
assert_eq!(j["hint"], "start dwarves-server");
assert!(e.line().contains("dwarf.v1/Dwarf.History"), "the line form names it too: {}", e.line());
}
#[test]
fn round_trips() {
let e = EmptyState::no_rows("mimir/binary_facts", "0 rows in workspace nordisk");
let back: EmptyState = serde_json::from_str(&serde_json::to_string(&e).unwrap()).unwrap();
assert_eq!(e, back);
}
}