use std::collections::HashSet;
use std::path::PathBuf;
use anyhow::{Result, bail};
#[derive(Clone, Debug)]
pub enum InspectorTabEntry {
Custom {
id: String,
label: String,
icon: Option<String>,
root: PathBuf,
},
HideBuiltin {
id: String,
},
}
pub const BUILTIN_TAB_IDS: &[&str] = &[
"workflow",
"database",
"state",
"queue",
"connections",
"console",
];
fn is_valid_custom_tab_id(id: &str) -> bool {
!id.is_empty()
&& id
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
}
impl InspectorTabEntry {
pub fn id(&self) -> &str {
match self {
Self::Custom { id, .. } | Self::HideBuiltin { id } => id,
}
}
fn validate(&self) -> Result<()> {
match self {
Self::Custom {
id,
label,
icon,
root,
} => {
if !is_valid_custom_tab_id(id) {
bail!(
"inspector tab id {id:?} is invalid: must be non-empty and contain only [a-zA-Z0-9_-]"
);
}
if BUILTIN_TAB_IDS.contains(&id.as_str()) {
bail!(
"inspector tab id {id:?} collides with a built-in tab; use {{ id: {id:?}, hidden: true }} to hide instead"
);
}
if label.is_empty() {
bail!("inspector tab {id:?} has an empty label");
}
if root.as_os_str().is_empty() {
bail!("inspector tab {id:?} has an empty source path");
}
if let Some(icon) = icon
&& icon.is_empty()
{
bail!("inspector tab {id:?} has an empty icon string");
}
Ok(())
}
Self::HideBuiltin { id } => {
if !BUILTIN_TAB_IDS.contains(&id.as_str()) {
bail!(
"inspector tab hide id {id:?} is not a known built-in (one of {:?})",
BUILTIN_TAB_IDS
);
}
Ok(())
}
}
}
}
pub fn validate_inspector_tabs(entries: &[InspectorTabEntry]) -> Result<()> {
let mut seen = HashSet::new();
for entry in entries {
entry.validate()?;
if !seen.insert(entry.id()) {
bail!("inspector tabs contain duplicate id {:?}", entry.id());
}
}
Ok(())
}
#[cfg(test)]
#[path = "../../tests/inspector_tabs.rs"]
mod tests;