bijux_cli/contracts/
command.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4#[derive(
6 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
7)]
8pub struct Namespace(pub String);
9
10impl Namespace {
11 pub fn new(raw: &str) -> Result<Self, String> {
13 let normalized = Self::normalize(raw);
14 if normalized.is_empty() {
15 return Err("namespace cannot be empty".to_string());
16 }
17 if !normalized.chars().all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '-')
18 {
19 return Err("namespace must be lowercase kebab-case".to_string());
20 }
21 if normalized.starts_with('-') || normalized.ends_with('-') || normalized.contains("--") {
22 return Err(
23 "namespace cannot start/end with '-' or contain consecutive '-'".to_string()
24 );
25 }
26 Ok(Self(normalized))
27 }
28
29 #[must_use]
31 pub fn normalize(raw: &str) -> String {
32 raw.trim()
33 .to_ascii_lowercase()
34 .replace(['_', ' ', '/'], "-")
35 .split('-')
36 .filter(|segment| !segment.is_empty())
37 .collect::<Vec<_>>()
38 .join("-")
39 }
40
41 #[must_use]
43 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
50pub struct CommandPath {
51 pub segments: Vec<Namespace>,
53}
54
55impl CommandPath {
56 pub fn new(raw_segments: &[&str]) -> Result<Self, String> {
58 let mut segments = Vec::with_capacity(raw_segments.len());
59 for value in raw_segments {
60 segments.push(Namespace::new(value)?);
61 }
62 if segments.is_empty() {
63 return Err("command path requires at least one segment".to_string());
64 }
65 Ok(Self { segments })
66 }
67
68 pub fn parse(raw: &str) -> Result<Self, String> {
70 let input = raw.trim();
71 if input.is_empty() {
72 return Err("command path cannot be empty".to_string());
73 }
74 let segments: Vec<&str> = input
75 .split(|ch: char| ch.is_ascii_whitespace() || ch == '/')
76 .filter(|segment| !segment.is_empty())
77 .collect();
78 Self::new(&segments)
79 }
80
81 #[must_use]
83 pub fn to_command_string(&self) -> String {
84 self.segments.iter().map(Namespace::as_str).collect::<Vec<_>>().join(" ")
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
90pub struct CommandMetadata {
91 pub path: CommandPath,
93 pub summary: String,
95 pub hidden: bool,
97 pub aliases: Vec<CommandPath>,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
103pub struct NamespaceMetadata {
104 pub name: Namespace,
106 pub reserved: bool,
108 pub owner: String,
110}