1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct SurfDoc {
8 pub front_matter: Option<FrontMatter>,
10 pub blocks: Vec<Block>,
12 pub source: String,
14}
15
16#[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 #[serde(flatten)]
66 pub extra: HashMap<String, serde_yaml::Value>,
67}
68
69#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(tag = "kind")]
136pub enum Block {
137 Unknown {
139 name: String,
140 attrs: Attrs,
141 content: String,
142 span: Span,
143 },
144 Markdown {
146 content: String,
147 span: Span,
148 },
149 Callout {
151 callout_type: CalloutType,
152 title: Option<String>,
153 content: String,
154 span: Span,
155 },
156 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 {
168 lang: Option<String>,
169 file: Option<String>,
170 highlight: Vec<String>,
171 content: String,
172 span: Span,
173 },
174 Tasks {
176 items: Vec<TaskItem>,
177 span: Span,
178 },
179 Decision {
181 status: DecisionStatus,
182 date: Option<String>,
183 deciders: Vec<String>,
184 content: String,
185 span: Span,
186 },
187 Metric {
189 label: String,
190 value: String,
191 trend: Option<Trend>,
192 unit: Option<String>,
193 span: Span,
194 },
195 Summary {
197 content: String,
198 span: Span,
199 },
200 Figure {
202 src: String,
203 caption: Option<String>,
204 alt: Option<String>,
205 width: Option<String>,
206 span: Span,
207 },
208 Tabs {
210 tabs: Vec<TabPanel>,
211 span: Span,
212 },
213 Columns {
215 columns: Vec<ColumnContent>,
216 span: Span,
217 },
218 Quote {
220 content: String,
221 attribution: Option<String>,
222 cite: Option<String>,
223 span: Span,
224 },
225 Cta {
227 label: String,
228 href: String,
229 primary: bool,
230 span: Span,
231 },
232 HeroImage {
234 src: String,
235 alt: Option<String>,
236 span: Span,
237 },
238 Testimonial {
240 content: String,
241 author: Option<String>,
242 role: Option<String>,
243 company: Option<String>,
244 span: Span,
245 },
246 Style {
248 properties: Vec<StyleProperty>,
249 span: Span,
250 },
251 Faq {
253 items: Vec<FaqItem>,
254 span: Span,
255 },
256 PricingTable {
258 headers: Vec<String>,
259 rows: Vec<Vec<String>>,
260 span: Span,
261 },
262 Site {
264 domain: Option<String>,
265 properties: Vec<StyleProperty>,
266 span: Span,
267 },
268 Page {
270 route: String,
271 layout: Option<String>,
272 title: Option<String>,
273 sidebar: bool,
274 content: String,
276 children: Vec<Block>,
278 span: Span,
279 },
280}
281
282#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct TaskItem {
306 pub done: bool,
307 pub text: String,
308 pub assignee: Option<String>,
309}
310
311#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct TabPanel {
333 pub label: String,
334 pub content: String,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct ColumnContent {
340 pub content: String,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct StyleProperty {
346 pub key: String,
347 pub value: String,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct FaqItem {
353 pub question: String,
354 pub answer: String,
355}
356
357#[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
370pub type Attrs = BTreeMap<String, AttrValue>;
372
373#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
385pub struct Span {
386 pub start_line: usize,
388 pub end_line: usize,
390 pub start_offset: usize,
392 pub end_offset: usize,
394}