Skip to main content

bcp_types/
block_type.rs

1/// Semantic block type identifiers.
2///
3/// Each variant maps to the wire byte value defined in RFC §4.4 and
4/// mirrored by the `bcp_wire::block_frame::block_type` constants.
5/// Unknown values are captured by `Unknown(u8)` for forward compatibility —
6/// a newer encoder may produce block types this version doesn't recognize,
7/// and we preserve them rather than discarding.
8///
9/// ```text
10/// ┌──────┬──────────────────┬──────────────────────────────────┐
11/// │ Wire │ Variant          │ Description                      │
12/// ├──────┼──────────────────┼──────────────────────────────────┤
13/// │ 0x01 │ Code             │ Source code with language/path    │
14/// │ 0x02 │ Conversation     │ Chat turn with role              │
15/// │ 0x03 │ FileTree         │ Directory structure              │
16/// │ 0x04 │ ToolResult       │ Tool/MCP output                  │
17/// │ 0x05 │ Document         │ Prose/markdown content           │
18/// │ 0x06 │ StructuredData   │ JSON/YAML/TOML/CSV data          │
19/// │ 0x07 │ Diff             │ Code changes with hunks          │
20/// │ 0x08 │ Annotation       │ Metadata overlay                 │
21/// │ 0x09 │ EmbeddingRef     │ Vector reference                 │
22/// │ 0x0A │ Image            │ Image reference or embed         │
23/// │ 0xFE │ Extension        │ User-defined block               │
24/// │ 0xFF │ End              │ End-of-stream sentinel           │
25/// └──────┴──────────────────┴──────────────────────────────────┘
26/// ```
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub enum BlockType {
29    Code,
30    Conversation,
31    FileTree,
32    ToolResult,
33    Document,
34    StructuredData,
35    Diff,
36    Annotation,
37    EmbeddingRef,
38    Image,
39    Extension,
40    End,
41    /// Forward-compatible catch-all for block types this version
42    /// doesn't recognize. The raw wire byte is preserved so it can
43    /// be re-encoded without loss.
44    Unknown(u8),
45}
46
47impl BlockType {
48    /// Return the single-byte wire ID for this block type.
49    ///
50    /// For known variants this is the constant from RFC §4.4.
51    /// For `Unknown(id)`, returns the captured byte as-is.
52    pub fn wire_id(&self) -> u8 {
53        match self {
54            Self::Code => 0x01,
55            Self::Conversation => 0x02,
56            Self::FileTree => 0x03,
57            Self::ToolResult => 0x04,
58            Self::Document => 0x05,
59            Self::StructuredData => 0x06,
60            Self::Diff => 0x07,
61            Self::Annotation => 0x08,
62            Self::EmbeddingRef => 0x09,
63            Self::Image => 0x0A,
64            Self::Extension => 0xFE,
65            Self::End => 0xFF,
66            Self::Unknown(id) => *id,
67        }
68    }
69
70    /// Parse a wire byte into a [`BlockType`].
71    ///
72    /// Known values map to their named variant. Anything else becomes
73    /// `Unknown(id)`, preserving the raw value for round-tripping.
74    pub fn from_wire_id(id: u8) -> Self {
75        match id {
76            0x01 => Self::Code,
77            0x02 => Self::Conversation,
78            0x03 => Self::FileTree,
79            0x04 => Self::ToolResult,
80            0x05 => Self::Document,
81            0x06 => Self::StructuredData,
82            0x07 => Self::Diff,
83            0x08 => Self::Annotation,
84            0x09 => Self::EmbeddingRef,
85            0x0A => Self::Image,
86            0xFE => Self::Extension,
87            0xFF => Self::End,
88            other => Self::Unknown(other),
89        }
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn all_known_variants_roundtrip() {
99        let variants = [
100            (BlockType::Code, 0x01),
101            (BlockType::Conversation, 0x02),
102            (BlockType::FileTree, 0x03),
103            (BlockType::ToolResult, 0x04),
104            (BlockType::Document, 0x05),
105            (BlockType::StructuredData, 0x06),
106            (BlockType::Diff, 0x07),
107            (BlockType::Annotation, 0x08),
108            (BlockType::EmbeddingRef, 0x09),
109            (BlockType::Image, 0x0A),
110            (BlockType::Extension, 0xFE),
111            (BlockType::End, 0xFF),
112        ];
113
114        for (variant, wire) in variants {
115            assert_eq!(variant.wire_id(), wire, "wire_id mismatch for {variant:?}");
116            assert_eq!(
117                BlockType::from_wire_id(wire),
118                variant,
119                "from_wire_id mismatch for {wire:#04X}"
120            );
121        }
122    }
123
124    #[test]
125    fn unknown_value_preserved() {
126        let unknown = BlockType::from_wire_id(0x42);
127        assert_eq!(unknown, BlockType::Unknown(0x42));
128        assert_eq!(unknown.wire_id(), 0x42);
129    }
130
131    #[test]
132    fn unknown_zero_preserved() {
133        let unknown = BlockType::from_wire_id(0x00);
134        assert_eq!(unknown, BlockType::Unknown(0x00));
135        assert_eq!(unknown.wire_id(), 0x00);
136    }
137}