1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct UclDocument {
9 pub structure: HashMap<String, Vec<String>>,
11 pub blocks: Vec<BlockDef>,
13 pub commands: Vec<Command>,
15}
16
17impl UclDocument {
18 pub fn new() -> Self {
19 Self {
20 structure: HashMap::new(),
21 blocks: Vec::new(),
22 commands: Vec::new(),
23 }
24 }
25}
26
27impl Default for UclDocument {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
35pub struct BlockDef {
36 pub content_type: ContentType,
38 pub id: String,
40 pub properties: HashMap<String, Value>,
42 pub content: String,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum ContentType {
50 Text,
51 Table,
52 Code,
53 Math,
54 Media,
55 Json,
56 Binary,
57 Composite,
58}
59
60impl ContentType {
61 pub fn parse_content_type(s: &str) -> Option<Self> {
62 match s.to_lowercase().as_str() {
63 "text" => Some(Self::Text),
64 "table" => Some(Self::Table),
65 "code" => Some(Self::Code),
66 "math" => Some(Self::Math),
67 "media" => Some(Self::Media),
68 "json" => Some(Self::Json),
69 "binary" => Some(Self::Binary),
70 "composite" => Some(Self::Composite),
71 _ => None,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub enum Command {
79 Edit(EditCommand),
81 Move(MoveCommand),
82 Append(AppendCommand),
83 Delete(DeleteCommand),
84 Prune(PruneCommand),
85 Fold(FoldCommand),
86 Link(LinkCommand),
87 Unlink(UnlinkCommand),
88 Snapshot(SnapshotCommand),
89 Transaction(TransactionCommand),
90 Atomic(Vec<Command>),
91 WriteSection(WriteSectionCommand),
92
93 Goto(GotoCommand),
95 Back(BackCommand),
96 Expand(ExpandCommand),
97 Follow(FollowCommand),
98 Path(PathFindCommand),
99 Search(SearchCommand),
100 Find(FindCommand),
101 View(ViewCommand),
102
103 Context(ContextCommand),
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct EditCommand {
110 pub block_id: String,
111 pub path: Path,
112 pub operator: Operator,
113 pub value: Value,
114 pub condition: Option<Condition>,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub struct MoveCommand {
120 pub block_id: String,
121 pub target: MoveTarget,
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub enum MoveTarget {
127 ToParent {
128 parent_id: String,
129 index: Option<usize>,
130 },
131 Before {
132 sibling_id: String,
133 },
134 After {
135 sibling_id: String,
136 },
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct AppendCommand {
142 pub parent_id: String,
143 pub content_type: ContentType,
144 pub properties: HashMap<String, Value>,
145 pub content: String,
146 pub index: Option<usize>,
147}
148
149#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct DeleteCommand {
152 pub block_id: Option<String>,
153 pub cascade: bool,
154 pub preserve_children: bool,
155 pub condition: Option<Condition>,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct PruneCommand {
161 pub target: PruneTarget,
162 pub dry_run: bool,
163}
164
165#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub enum PruneTarget {
168 Unreachable,
169 Where(Condition),
170}
171
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
174pub struct FoldCommand {
175 pub block_id: String,
176 pub depth: Option<usize>,
177 pub max_tokens: Option<usize>,
178 pub preserve_tags: Vec<String>,
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct LinkCommand {
184 pub source_id: String,
185 pub edge_type: String,
186 pub target_id: String,
187 pub metadata: HashMap<String, Value>,
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub struct UnlinkCommand {
193 pub source_id: String,
194 pub edge_type: String,
195 pub target_id: String,
196}
197
198#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
200pub enum SnapshotCommand {
201 Create {
202 name: String,
203 description: Option<String>,
204 },
205 Restore {
206 name: String,
207 },
208 List,
209 Delete {
210 name: String,
211 },
212 Diff {
213 name1: String,
214 name2: String,
215 },
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220pub enum TransactionCommand {
221 Begin { name: Option<String> },
222 Commit { name: Option<String> },
223 Rollback { name: Option<String> },
224}
225
226#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
228pub struct WriteSectionCommand {
229 pub section_id: String,
231 pub markdown: String,
233 pub base_heading_level: Option<usize>,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243pub struct GotoCommand {
244 pub block_id: String,
246}
247
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250pub struct BackCommand {
251 pub steps: usize,
253}
254
255impl Default for BackCommand {
256 fn default() -> Self {
257 Self { steps: 1 }
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
263#[serde(rename_all = "lowercase")]
264#[derive(Default)]
265pub enum ExpandDirection {
266 #[default]
268 Down,
269 Up,
271 Both,
273 Semantic,
275}
276
277impl ExpandDirection {
278 pub fn parse(s: &str) -> Option<Self> {
279 match s.to_lowercase().as_str() {
280 "down" => Some(Self::Down),
281 "up" => Some(Self::Up),
282 "both" => Some(Self::Both),
283 "semantic" => Some(Self::Semantic),
284 _ => None,
285 }
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
291#[serde(rename_all = "lowercase")]
292#[derive(Default)]
293pub enum ViewMode {
294 #[default]
296 Full,
297 Preview { length: usize },
299 Metadata,
301 IdsOnly,
303}
304
305impl ViewMode {
306 pub fn parse(s: &str) -> Option<Self> {
307 match s.to_lowercase().as_str() {
308 "full" => Some(Self::Full),
309 "preview" => Some(Self::Preview { length: 100 }),
310 "metadata" => Some(Self::Metadata),
311 "ids" | "idsonly" | "ids_only" => Some(Self::IdsOnly),
312 _ => None,
313 }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
319pub struct TraversalFilterCriteria {
320 pub include_roles: Vec<String>,
322 pub exclude_roles: Vec<String>,
324 pub include_tags: Vec<String>,
326 pub exclude_tags: Vec<String>,
328 pub content_pattern: Option<String>,
330 pub edge_types: Vec<String>,
332}
333
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336pub struct ExpandCommand {
337 pub block_id: String,
339 pub direction: ExpandDirection,
341 pub depth: usize,
343 pub mode: Option<ViewMode>,
345 pub filter: Option<TraversalFilterCriteria>,
347}
348
349impl Default for ExpandCommand {
350 fn default() -> Self {
351 Self {
352 block_id: String::new(),
353 direction: ExpandDirection::Down,
354 depth: 1,
355 mode: None,
356 filter: None,
357 }
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
363pub struct FollowCommand {
364 pub source_id: String,
366 pub edge_types: Vec<String>,
368 pub target_id: Option<String>,
370}
371
372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
374pub struct PathFindCommand {
375 pub from_id: String,
377 pub to_id: String,
379 pub max_length: Option<usize>,
381}
382
383#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub struct SearchCommand {
386 pub query: String,
388 pub limit: Option<usize>,
390 pub min_similarity: Option<f32>,
392 pub filter: Option<TraversalFilterCriteria>,
394}
395
396impl Default for SearchCommand {
397 fn default() -> Self {
398 Self {
399 query: String::new(),
400 limit: Some(10),
401 min_similarity: None,
402 filter: None,
403 }
404 }
405}
406
407#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
409pub struct FindCommand {
410 pub role: Option<String>,
412 pub tag: Option<String>,
414 pub label: Option<String>,
416 pub pattern: Option<String>,
418}
419
420#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422pub enum ViewTarget {
423 Block(String),
425 Neighborhood,
427}
428
429#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
431pub struct ViewCommand {
432 pub target: ViewTarget,
434 pub mode: ViewMode,
436 pub depth: Option<usize>,
438}
439
440impl Default for ViewCommand {
441 fn default() -> Self {
442 Self {
443 target: ViewTarget::Neighborhood,
444 mode: ViewMode::Full,
445 depth: None,
446 }
447 }
448}
449
450#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456pub enum ContextCommand {
457 Add(ContextAddCommand),
459 Remove { block_id: String },
461 Clear,
463 Expand(ContextExpandCommand),
465 Compress { method: CompressionMethod },
467 Prune(ContextPruneCommand),
469 Render { format: Option<RenderFormat> },
471 Stats,
473 Focus { block_id: Option<String> },
475}
476
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
479pub enum ContextAddTarget {
480 Block(String),
482 Results,
484 Children { parent_id: String },
486 Path { from_id: String, to_id: String },
488}
489
490#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
492pub struct ContextAddCommand {
493 pub target: ContextAddTarget,
495 pub reason: Option<String>,
497 pub relevance: Option<f32>,
499}
500
501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
503pub struct ContextExpandCommand {
504 pub direction: ExpandDirection,
506 pub depth: Option<usize>,
508 pub token_budget: Option<usize>,
510}
511
512#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
514pub struct ContextPruneCommand {
515 pub min_relevance: Option<f32>,
517 pub max_age_secs: Option<u64>,
519}
520
521#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
523#[serde(rename_all = "lowercase")]
524pub enum CompressionMethod {
525 Truncate,
527 Summarize,
529 StructureOnly,
531}
532
533impl CompressionMethod {
534 pub fn parse(s: &str) -> Option<Self> {
535 match s.to_lowercase().as_str() {
536 "truncate" => Some(Self::Truncate),
537 "summarize" => Some(Self::Summarize),
538 "structure_only" | "structureonly" | "structure" => Some(Self::StructureOnly),
539 _ => None,
540 }
541 }
542}
543
544#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
546#[serde(rename_all = "lowercase")]
547#[derive(Default)]
548pub enum RenderFormat {
549 #[default]
551 Default,
552 ShortIds,
554 Markdown,
556}
557
558impl RenderFormat {
559 pub fn parse(s: &str) -> Option<Self> {
560 match s.to_lowercase().as_str() {
561 "default" => Some(Self::Default),
562 "short_ids" | "shortids" | "short" => Some(Self::ShortIds),
563 "markdown" | "md" => Some(Self::Markdown),
564 _ => None,
565 }
566 }
567}
568
569#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
571pub struct Path {
572 pub segments: Vec<PathSegment>,
573}
574
575impl Path {
576 pub fn new(segments: Vec<PathSegment>) -> Self {
577 Self { segments }
578 }
579
580 pub fn simple(name: &str) -> Self {
581 Self {
582 segments: vec![PathSegment::Property(name.to_string())],
583 }
584 }
585}
586
587impl std::fmt::Display for Path {
588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589 write!(
590 f,
591 "{}",
592 self.segments
593 .iter()
594 .map(|s| match s {
595 PathSegment::Property(p) => p.clone(),
596 PathSegment::Index(i) => format!("[{}]", i),
597 PathSegment::Slice { start, end } => match (start, end) {
598 (Some(s), Some(e)) => format!("[{}:{}]", s, e),
599 (Some(s), None) => format!("[{}:]", s),
600 (None, Some(e)) => format!("[:{}]", e),
601 (None, None) => "[:]".to_string(),
602 },
603 PathSegment::JsonPath(p) => format!("${}", p),
604 })
605 .collect::<Vec<_>>()
606 .join(".")
607 )
608 }
609}
610
611#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
613pub enum PathSegment {
614 Property(String),
615 Index(i64),
616 Slice {
617 start: Option<i64>,
618 end: Option<i64>,
619 },
620 JsonPath(String),
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
625pub enum Operator {
626 Set, Append, Remove, Increment, Decrement, }
632
633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
635#[serde(untagged)]
636pub enum Value {
637 Null,
638 Bool(bool),
639 Number(f64),
640 String(String),
641 Array(Vec<Value>),
642 Object(HashMap<String, Value>),
643 BlockRef(String),
644}
645
646impl Value {
647 pub fn to_json(&self) -> serde_json::Value {
648 match self {
649 Value::Null => serde_json::Value::Null,
650 Value::Bool(b) => serde_json::Value::Bool(*b),
651 Value::Number(n) => serde_json::json!(*n),
652 Value::String(s) => serde_json::Value::String(s.clone()),
653 Value::Array(arr) => {
654 serde_json::Value::Array(arr.iter().map(|v| v.to_json()).collect())
655 }
656 Value::Object(obj) => {
657 let map: serde_json::Map<String, serde_json::Value> =
658 obj.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();
659 serde_json::Value::Object(map)
660 }
661 Value::BlockRef(id) => serde_json::json!({"$ref": id}),
662 }
663 }
664}
665
666#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
668pub enum Condition {
669 Comparison {
670 path: Path,
671 op: ComparisonOp,
672 value: Value,
673 },
674 Contains {
675 path: Path,
676 value: Value,
677 },
678 StartsWith {
679 path: Path,
680 prefix: String,
681 },
682 EndsWith {
683 path: Path,
684 suffix: String,
685 },
686 Matches {
687 path: Path,
688 regex: String,
689 },
690 Exists {
691 path: Path,
692 },
693 IsNull {
694 path: Path,
695 },
696 And(Box<Condition>, Box<Condition>),
697 Or(Box<Condition>, Box<Condition>),
698 Not(Box<Condition>),
699}
700
701#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
703pub enum ComparisonOp {
704 Eq, Ne, Gt, Ge, Lt, Le, }
711
712#[cfg(test)]
713mod tests {
714 use super::*;
715
716 #[test]
717 fn test_path_simple() {
718 let path = Path::simple("content.text");
719 assert_eq!(path.segments.len(), 1);
720 }
721
722 #[test]
723 fn test_value_to_json() {
724 let value = Value::Object(
725 [("key".to_string(), Value::String("value".to_string()))]
726 .into_iter()
727 .collect(),
728 );
729 let json = value.to_json();
730 assert_eq!(json["key"], "value");
731 }
732}