rivetkit_core/inspector/
tabs.rs1use std::collections::HashSet;
2use std::path::PathBuf;
3
4use anyhow::{Result, bail};
5
6#[derive(Clone, Debug)]
13pub enum InspectorTabEntry {
14 Custom {
15 id: String,
16 label: String,
17 icon: Option<String>,
21 root: PathBuf,
22 },
23 HideBuiltin {
24 id: String,
25 },
26}
27
28pub const BUILTIN_TAB_IDS: &[&str] = &[
32 "workflow",
33 "database",
34 "state",
35 "queue",
36 "connections",
37 "console",
38];
39
40fn is_valid_custom_tab_id(id: &str) -> bool {
44 !id.is_empty()
45 && id
46 .chars()
47 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
48}
49
50impl InspectorTabEntry {
51 pub fn id(&self) -> &str {
52 match self {
53 Self::Custom { id, .. } | Self::HideBuiltin { id } => id,
54 }
55 }
56
57 fn validate(&self) -> Result<()> {
58 match self {
59 Self::Custom {
60 id,
61 label,
62 icon,
63 root,
64 } => {
65 if !is_valid_custom_tab_id(id) {
66 bail!(
67 "inspector tab id {id:?} is invalid: must be non-empty and contain only [a-zA-Z0-9_-]"
68 );
69 }
70 if BUILTIN_TAB_IDS.contains(&id.as_str()) {
71 bail!(
72 "inspector tab id {id:?} collides with a built-in tab; use {{ id: {id:?}, hidden: true }} to hide instead"
73 );
74 }
75 if label.is_empty() {
76 bail!("inspector tab {id:?} has an empty label");
77 }
78 if root.as_os_str().is_empty() {
79 bail!("inspector tab {id:?} has an empty source path");
80 }
81 if let Some(icon) = icon
82 && icon.is_empty()
83 {
84 bail!("inspector tab {id:?} has an empty icon string");
85 }
86 Ok(())
87 }
88 Self::HideBuiltin { id } => {
89 if !BUILTIN_TAB_IDS.contains(&id.as_str()) {
90 bail!(
91 "inspector tab hide id {id:?} is not a known built-in (one of {:?})",
92 BUILTIN_TAB_IDS
93 );
94 }
95 Ok(())
96 }
97 }
98 }
99}
100
101pub fn validate_inspector_tabs(entries: &[InspectorTabEntry]) -> Result<()> {
105 let mut seen = HashSet::new();
106 for entry in entries {
107 entry.validate()?;
108 if !seen.insert(entry.id()) {
109 bail!("inspector tabs contain duplicate id {:?}", entry.id());
110 }
111 }
112 Ok(())
113}
114
115#[cfg(test)]
117#[path = "../../tests/inspector_tabs.rs"]
118mod tests;