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 "schedules",
37 "connections",
38 "console",
39];
40
41fn is_valid_custom_tab_id(id: &str) -> bool {
45 !id.is_empty()
46 && id
47 .chars()
48 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
49}
50
51impl InspectorTabEntry {
52 pub fn id(&self) -> &str {
53 match self {
54 Self::Custom { id, .. } | Self::HideBuiltin { id } => id,
55 }
56 }
57
58 fn validate(&self) -> Result<()> {
59 match self {
60 Self::Custom {
61 id,
62 label,
63 icon,
64 root,
65 } => {
66 if !is_valid_custom_tab_id(id) {
67 bail!(
68 "inspector tab id {id:?} is invalid: must be non-empty and contain only [a-zA-Z0-9_-]"
69 );
70 }
71 if BUILTIN_TAB_IDS.contains(&id.as_str()) {
72 bail!(
73 "inspector tab id {id:?} collides with a built-in tab; use {{ id: {id:?}, hidden: true }} to hide instead"
74 );
75 }
76 if label.is_empty() {
77 bail!("inspector tab {id:?} has an empty label");
78 }
79 if root.as_os_str().is_empty() {
80 bail!("inspector tab {id:?} has an empty source path");
81 }
82 if let Some(icon) = icon
83 && icon.is_empty()
84 {
85 bail!("inspector tab {id:?} has an empty icon string");
86 }
87 Ok(())
88 }
89 Self::HideBuiltin { id } => {
90 if !BUILTIN_TAB_IDS.contains(&id.as_str()) {
91 bail!(
92 "inspector tab hide id {id:?} is not a known built-in (one of {:?})",
93 BUILTIN_TAB_IDS
94 );
95 }
96 Ok(())
97 }
98 }
99 }
100}
101
102pub fn validate_inspector_tabs(entries: &[InspectorTabEntry]) -> Result<()> {
106 let mut seen = HashSet::new();
107 for entry in entries {
108 entry.validate()?;
109 if !seen.insert(entry.id()) {
110 bail!("inspector tabs contain duplicate id {:?}", entry.id());
111 }
112 }
113 Ok(())
114}
115
116#[cfg(test)]
118#[path = "../../tests/inspector_tabs.rs"]
119mod tests;