Skip to main content

ito_common/id/
spec_id.rs

1use std::fmt;
2
3use super::IdParseError;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6/// A spec identifier (directory name under `.ito/specs/`).
7pub struct SpecId(String);
8
9impl SpecId {
10    /// Borrow the underlying string.
11    pub fn as_str(&self) -> &str {
12        &self.0
13    }
14}
15
16impl fmt::Display for SpecId {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        f.write_str(&self.0)
19    }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23/// Parsed representation of a spec identifier.
24pub struct ParsedSpecId {
25    /// The parsed spec id.
26    pub spec_id: SpecId,
27}
28
29/// Parse a spec identifier.
30///
31/// This is intentionally permissive: any non-empty directory name is accepted
32/// as a spec id.
33pub fn parse_spec_id(input: &str) -> Result<ParsedSpecId, IdParseError> {
34    let trimmed = input.trim();
35    if trimmed.is_empty() {
36        return Err(IdParseError::new(
37            "Spec ID cannot be empty",
38            Some("Provide a spec ID like \"cli-init\""),
39        ));
40    }
41
42    // TS accepts any directory name with a spec.md inside it. We treat the ID
43    // as the directory name and do not normalize it.
44    Ok(ParsedSpecId {
45        spec_id: SpecId(trimmed.to_string()),
46    })
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn parse_spec_id_preserves_value() {
55        let parsed = parse_spec_id("cli-init").unwrap();
56        assert_eq!(parsed.spec_id.as_str(), "cli-init");
57    }
58}