Skip to main content

surf_parse/
types.rs

1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5/// A parsed SurfDoc document.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct SurfDoc {
8    /// Parsed YAML front matter, if present.
9    pub front_matter: Option<FrontMatter>,
10    /// Ordered sequence of blocks in the document body.
11    pub blocks: Vec<Block>,
12    /// Original source text that was parsed.
13    pub source: String,
14}
15
16/// YAML front matter fields.
17///
18/// Known fields are typed; unknown fields are captured in `extra`.
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20#[serde(default)]
21pub struct FrontMatter {
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub title: Option<String>,
24
25    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
26    pub doc_type: Option<DocType>,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub status: Option<DocStatus>,
30
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub scope: Option<Scope>,
33
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub tags: Option<Vec<String>>,
36
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub created: Option<String>,
39
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub updated: Option<String>,
42
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub author: Option<String>,
45
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub confidence: Option<Confidence>,
48
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub related: Option<Vec<Related>>,
51
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub version: Option<u32>,
54
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub contributors: Option<Vec<String>>,
57
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub workspace: Option<String>,
60
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub decision: Option<String>,
63
64    /// Any front matter fields not covered by typed fields above.
65    #[serde(flatten)]
66    pub extra: HashMap<String, serde_yaml::Value>,
67}
68
69/// A cross-reference to another document.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Related {
72    pub path: String,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub relationship: Option<Relationship>,
75}
76
77/// Relationship type for cross-references.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
79#[serde(rename_all = "lowercase")]
80pub enum Relationship {
81    Produces,
82    Consumes,
83    References,
84    Supersedes,
85}
86
87/// SurfDoc document types (front matter `type` field).
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum DocType {
91    Doc,
92    Guide,
93    Conversation,
94    Plan,
95    Agent,
96    Preference,
97    Report,
98    Proposal,
99    Incident,
100    Review,
101}
102
103/// Document lifecycle status.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum DocStatus {
107    Draft,
108    Active,
109    Closed,
110    Archived,
111}
112
113/// Visibility/access scope.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "kebab-case")]
116pub enum Scope {
117    Personal,
118    WorkspacePrivate,
119    Workspace,
120    Repo,
121    Public,
122}
123
124/// Confidence level for guides and estimates.
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "lowercase")]
127pub enum Confidence {
128    Low,
129    Medium,
130    High,
131}
132
133/// A parsed block in the document body.
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(tag = "kind")]
136pub enum Block {
137    /// A block directive that has not yet been typed (Chunk 1 catch-all).
138    Unknown {
139        name: String,
140        attrs: Attrs,
141        content: String,
142        span: Span,
143    },
144    /// Plain markdown content between directives.
145    Markdown {
146        content: String,
147        span: Span,
148    },
149    /// Callout/admonition box.
150    Callout {
151        callout_type: CalloutType,
152        title: Option<String>,
153        content: String,
154        span: Span,
155    },
156    /// Structured data table (CSV/JSON/inline rows).
157    Data {
158        id: Option<String>,
159        format: DataFormat,
160        sortable: bool,
161        headers: Vec<String>,
162        rows: Vec<Vec<String>>,
163        raw_content: String,
164        span: Span,
165    },
166    /// Code block with optional language and file path.
167    Code {
168        lang: Option<String>,
169        file: Option<String>,
170        highlight: Vec<String>,
171        content: String,
172        span: Span,
173    },
174    /// Task list with checkbox items.
175    Tasks {
176        items: Vec<TaskItem>,
177        span: Span,
178    },
179    /// Decision record.
180    Decision {
181        status: DecisionStatus,
182        date: Option<String>,
183        deciders: Vec<String>,
184        content: String,
185        span: Span,
186    },
187    /// Single metric display.
188    Metric {
189        label: String,
190        value: String,
191        trend: Option<Trend>,
192        unit: Option<String>,
193        span: Span,
194    },
195    /// Executive summary block.
196    Summary {
197        content: String,
198        span: Span,
199    },
200    /// Figure with image source and caption.
201    Figure {
202        src: String,
203        caption: Option<String>,
204        alt: Option<String>,
205        width: Option<String>,
206        span: Span,
207    },
208    /// Tabbed content with named panels.
209    Tabs {
210        tabs: Vec<TabPanel>,
211        span: Span,
212    },
213    /// Multi-column layout.
214    Columns {
215        columns: Vec<ColumnContent>,
216        span: Span,
217    },
218    /// Attributed quote with optional source.
219    Quote {
220        content: String,
221        attribution: Option<String>,
222        cite: Option<String>,
223        span: Span,
224    },
225    /// Call-to-action button.
226    Cta {
227        label: String,
228        href: String,
229        primary: bool,
230        span: Span,
231    },
232    /// Hero image visual.
233    HeroImage {
234        src: String,
235        alt: Option<String>,
236        span: Span,
237    },
238    /// Customer testimonial.
239    Testimonial {
240        content: String,
241        author: Option<String>,
242        role: Option<String>,
243        company: Option<String>,
244        span: Span,
245    },
246    /// Presentation style overrides (key-value pairs).
247    Style {
248        properties: Vec<StyleProperty>,
249        span: Span,
250    },
251    /// FAQ accordion with question/answer pairs.
252    Faq {
253        items: Vec<FaqItem>,
254        span: Span,
255    },
256    /// Pricing comparison table.
257    PricingTable {
258        headers: Vec<String>,
259        rows: Vec<Vec<String>>,
260        span: Span,
261    },
262    /// Site-level configuration (one per document).
263    Site {
264        domain: Option<String>,
265        properties: Vec<StyleProperty>,
266        span: Span,
267    },
268    /// Page/route definition — container block with child blocks.
269    Page {
270        route: String,
271        layout: Option<String>,
272        title: Option<String>,
273        sidebar: bool,
274        /// Raw content for degradation renderers.
275        content: String,
276        /// Parsed child blocks (leaf directives resolved, rest as Markdown).
277        children: Vec<Block>,
278        span: Span,
279    },
280}
281
282/// Callout/admonition type.
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
284#[serde(rename_all = "lowercase")]
285pub enum CalloutType {
286    Info,
287    Warning,
288    Danger,
289    Tip,
290    Note,
291    Success,
292}
293
294/// Data block format.
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
296#[serde(rename_all = "lowercase")]
297pub enum DataFormat {
298    Table,
299    Csv,
300    Json,
301}
302
303/// A single task item within a `Tasks` block.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct TaskItem {
306    pub done: bool,
307    pub text: String,
308    pub assignee: Option<String>,
309}
310
311/// Decision record status.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "lowercase")]
314pub enum DecisionStatus {
315    Proposed,
316    Accepted,
317    Rejected,
318    Superseded,
319}
320
321/// Metric trend direction.
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
323#[serde(rename_all = "lowercase")]
324pub enum Trend {
325    Up,
326    Down,
327    Flat,
328}
329
330/// A single tab panel within a `Tabs` block.
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct TabPanel {
333    pub label: String,
334    pub content: String,
335}
336
337/// A single column in a `Columns` block.
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct ColumnContent {
340    pub content: String,
341}
342
343/// A key-value style override within a `Style` block.
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct StyleProperty {
346    pub key: String,
347    pub value: String,
348}
349
350/// A question/answer pair within a `Faq` block.
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct FaqItem {
353    pub question: String,
354    pub answer: String,
355}
356
357/// Inline extension found within text content.
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub enum InlineExt {
360    Evidence {
361        tier: Option<u8>,
362        source: Option<String>,
363        text: String,
364    },
365    Status {
366        value: String,
367    },
368}
369
370/// Ordered map of attribute key-value pairs.
371pub type Attrs = BTreeMap<String, AttrValue>;
372
373/// A value inside a block directive attribute.
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375#[serde(untagged)]
376pub enum AttrValue {
377    String(String),
378    Number(f64),
379    Bool(bool),
380    Null,
381}
382
383/// Source location of a block in the original document.
384#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
385pub struct Span {
386    /// 1-based starting line number.
387    pub start_line: usize,
388    /// 1-based ending line number (inclusive).
389    pub end_line: usize,
390    /// 0-based byte offset of the first character.
391    pub start_offset: usize,
392    /// 0-based byte offset past the last character.
393    pub end_offset: usize,
394}