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}