1use saphyr::LoadableYamlNode;
18use saphyr::MarkedYaml;
19use serde::Serialize;
20use serde_json::Value;
21
22use crate::ast::EtlDocument;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
29pub struct Span {
30 pub start: u32,
31 pub end: u32,
32 pub line: u32,
33 pub column: u32,
34 pub end_line: u32,
35 pub end_column: u32,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
40#[serde(rename_all = "lowercase")]
41pub enum ElementKind {
42 Section,
43 Definition,
44 Field,
45 Reference,
46}
47
48impl ElementKind {
49 fn rank(self) -> u8 {
50 match self {
51 ElementKind::Reference => 4,
52 ElementKind::Field => 3,
53 ElementKind::Definition => 2,
54 ElementKind::Section => 1,
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub enum PathPart {
62 Key(String),
63 Index(usize),
64}
65
66pub type PathKey = Vec<PathPart>;
68
69#[derive(Debug, Clone, Serialize)]
71pub struct IndexedElement {
72 #[serde(rename = "kind")]
73 pub kind: ElementKind,
74 pub name: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub field: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub tree: Option<String>,
79 pub span: Span,
80 #[serde(skip_serializing_if = "Option::is_none")]
83 pub key_span: Option<Span>,
84 #[serde(skip)]
86 pub depth: usize,
87 #[serde(skip)]
89 pub path: PathKey,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum SpanKey {
97 Section(&'static str),
98 Tree {
99 tree: String,
100 },
101 FaultTree {
102 tree: String,
103 },
104 InitiatingEvent {
105 tree: String,
106 field: &'static str,
107 },
108 TopEvent {
109 tree: String,
110 field: &'static str,
111 },
112 Node {
113 tree: String,
114 id: String,
115 },
116 NodeField {
117 tree: String,
118 id: String,
119 field: &'static str,
120 },
121 BranchField {
122 tree: String,
123 id: String,
124 branch: usize,
125 field: &'static str,
126 },
127 Gate {
128 tree: String,
129 id: String,
130 },
131 GateField {
132 tree: String,
133 id: String,
134 field: &'static str,
135 },
136 GateInput {
137 tree: String,
138 id: String,
139 idx: usize,
140 },
141 BasicEvent {
142 tree: String,
143 id: String,
144 },
145 BasicEventField {
146 tree: String,
147 id: String,
148 field: &'static str,
149 },
150 Transfer {
151 tree: String,
152 id: String,
153 field: &'static str,
154 },
155 ImportAlias {
156 alias: String,
157 },
158}
159
160impl SpanKey {
161 fn path(&self) -> PathKey {
162 let key = |s: &str| PathPart::Key(s.to_string());
163 match self {
164 SpanKey::Section(s) => vec![key(s)],
165 SpanKey::Tree { tree } => vec![key("event_trees"), key(tree)],
166 SpanKey::FaultTree { tree } => vec![key("fault_trees"), key(tree)],
167 SpanKey::InitiatingEvent { tree, field } => vec![
168 key("event_trees"),
169 key(tree),
170 key("initiating_event"),
171 key(field),
172 ],
173 SpanKey::TopEvent { tree, field } => {
174 vec![key("fault_trees"), key(tree), key("top_event"), key(field)]
175 }
176 SpanKey::Node { tree, id } => {
177 vec![key("event_trees"), key(tree), key("nodes"), key(id)]
178 }
179 SpanKey::NodeField { tree, id, field } => {
180 let mut p = SpanKey::Node {
181 tree: tree.clone(),
182 id: id.clone(),
183 }
184 .path();
185 p.push(key(field));
186 p
187 }
188 SpanKey::BranchField {
189 tree,
190 id,
191 branch,
192 field,
193 } => {
194 let mut p = SpanKey::Node {
195 tree: tree.clone(),
196 id: id.clone(),
197 }
198 .path();
199 p.push(key("branches"));
200 p.push(PathPart::Index(*branch));
201 p.push(key(field));
202 p
203 }
204 SpanKey::Gate { tree, id } => {
205 vec![key("fault_trees"), key(tree), key("gates"), key(id)]
206 }
207 SpanKey::GateField { tree, id, field } => {
208 let mut p = SpanKey::Gate {
209 tree: tree.clone(),
210 id: id.clone(),
211 }
212 .path();
213 p.push(key(field));
214 p
215 }
216 SpanKey::GateInput { tree, id, idx } => {
217 let mut p = SpanKey::Gate {
218 tree: tree.clone(),
219 id: id.clone(),
220 }
221 .path();
222 p.push(key("inputs"));
223 p.push(PathPart::Index(*idx));
224 p
225 }
226 SpanKey::BasicEvent { tree, id } => {
227 vec![key("fault_trees"), key(tree), key("basic_events"), key(id)]
228 }
229 SpanKey::BasicEventField { tree, id, field } => {
230 let mut p = SpanKey::BasicEvent {
231 tree: tree.clone(),
232 id: id.clone(),
233 }
234 .path();
235 p.push(key(field));
236 p
237 }
238 SpanKey::Transfer { tree, id, field } => vec![
239 key("fault_trees"),
240 key(tree),
241 key("transfers"),
242 key(id),
243 key(field),
244 ],
245 SpanKey::ImportAlias { alias } => vec![key("asyncapi_imports"), key(alias)],
246 }
247 }
248}
249
250#[derive(Debug, Default, Clone)]
252pub struct SpanIndex {
253 pub elements: Vec<IndexedElement>,
254 by_path: std::collections::HashMap<PathKey, usize>,
255 by_identity: std::collections::HashMap<(String, String), Vec<usize>>,
258}
259
260impl SpanIndex {
261 pub fn resolve(&self, key: &SpanKey) -> Option<&IndexedElement> {
263 self.by_path.get(&key.path()).map(|&i| &self.elements[i])
264 }
265
266 pub fn find_deepest(&self, offset: u32) -> Option<&IndexedElement> {
270 let mut best: Option<&IndexedElement> = None;
271 let mut best_size: u64 = u64::MAX;
272 let mut best_rank: u8 = 0;
273 let mut best_depth: usize = 0;
274 for el in &self.elements {
275 let mut size: Option<u64> = None;
276 if el.span.start <= offset && offset < el.span.end {
277 size = Some((el.span.end - el.span.start) as u64);
278 }
279 if let Some(ks) = &el.key_span {
280 if ks.start <= offset && offset < ks.end {
281 let s = (ks.end - ks.start) as u64;
282 if size.is_none_or(|cur| s < cur) {
283 size = Some(s);
284 }
285 }
286 }
287 if let Some(size) = size {
288 let better = size < best_size
289 || (size == best_size
290 && (el.kind.rank() > best_rank
291 || (el.kind.rank() == best_rank && el.depth > best_depth)));
292 if better {
293 best = Some(el);
294 best_size = size;
295 best_rank = el.kind.rank();
296 best_depth = el.depth;
297 }
298 }
299 }
300 best
301 }
302
303 pub fn by_identity(&self, tree: &str, id: &str) -> Vec<&IndexedElement> {
305 self.by_identity
306 .get(&(tree.to_string(), id.to_string()))
307 .map(|v| v.iter().map(|&i| &self.elements[i]).collect())
308 .unwrap_or_default()
309 }
310
311 pub fn definition(&self, tree: &str, id: &str) -> Option<&IndexedElement> {
313 self.by_identity
314 .get(&(tree.to_string(), id.to_string()))
315 .and_then(|v| {
316 v.iter()
317 .find(|&&i| self.elements[i].kind == ElementKind::Definition)
318 .map(|&i| &self.elements[i])
319 })
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct DuplicateId {
326 pub tree: String,
327 pub kind: String,
328 pub id: String,
329 pub span: Span,
330}
331
332pub fn parse_document_with_spans(content: &str) -> Result<(EtlDocument, SpanIndex), String> {
335 let doc = crate::parse_document(content)?;
336 let index = build_span_index(content)?;
337 Ok((doc, index))
338}
339
340pub fn build_span_index(content: &str) -> Result<SpanIndex, String> {
342 let mut parser = saphyr_parser::Parser::new_from_str(content);
351 let docs = MarkedYaml::load_from_parser(&mut parser).map_err(|e| e.to_string())?;
352 let root = docs.first().ok_or("empty ETDL document")?;
353 let mut builder = Builder::new(content);
354 builder.walk_root(root);
355 Ok(builder.index)
356}
357
358pub fn inject_spans(value: &mut Value, index: &SpanIndex) {
361 let mut path: PathKey = Vec::new();
362 walk_inject(value, index, &mut path);
363}
364
365fn walk_inject(value: &mut Value, index: &SpanIndex, path: &mut PathKey) {
366 if let Some(&idx) = index.by_path.get(path) {
367 let el = &index.elements[idx];
368 let span = serde_json::to_value(el.span).unwrap_or_default();
369 match value {
370 Value::Object(map) => {
371 map.insert("span".to_string(), span);
372 }
373 Value::Array(_) => {
374 }
376 _ => {
377 let inner = std::mem::replace(value, Value::Null);
378 let mut map = serde_json::Map::new();
379 map.insert("value".to_string(), inner);
380 map.insert("span".to_string(), span);
381 *value = Value::Object(map);
382 }
383 }
384 }
385 match value {
386 Value::Object(map) => {
387 for (k, v) in map.iter_mut() {
388 path.push(PathPart::Key(k.clone()));
389 walk_inject(v, index, path);
390 path.pop();
391 }
392 }
393 Value::Array(arr) => {
394 for (i, v) in arr.iter_mut().enumerate() {
395 path.push(PathPart::Index(i));
396 walk_inject(v, index, path);
397 path.pop();
398 }
399 }
400 _ => {}
401 }
402}
403
404pub fn detect_duplicate_ids(content: &str) -> Result<Vec<DuplicateId>, String> {
408 use saphyr_parser::{Event, Parser};
409
410 #[derive(Clone, Copy, PartialEq)]
411 enum Container {
412 Mapping,
413 Sequence,
414 }
415
416 #[derive(Default)]
417 struct MapCtx {
418 expect_key: bool,
419 seen: std::collections::BTreeMap<String, usize>,
420 path: Vec<String>,
421 kind: Option<String>,
422 tree: Option<String>,
423 }
424
425 let mut parser = Parser::new_from_str(content);
426 let mut duplicates = Vec::new();
427 let line_map = LineMap::new(content);
428 let mut maps: Vec<MapCtx> = Vec::new();
429 let mut containers: Vec<Container> = Vec::new();
430
431 while let Some(res) = parser.next_event() {
432 let (ev, span) = res.map_err(|e| e.to_string())?;
433 match ev {
434 Event::MappingStart(..) => {
435 if let Some(parent) = maps.last_mut() {
436 parent.expect_key = true;
438 }
439 let parent_path = maps.last().map(|c| c.path.clone()).unwrap_or_default();
440 let mut ctx = MapCtx {
441 expect_key: true,
442 path: parent_path,
443 ..Default::default()
444 };
445 if let Some(last) = ctx.path.last() {
446 let section_idx = ctx
447 .path
448 .iter()
449 .position(|p| p == "eventTrees" || p == "faultTrees");
450 if let Some(section_idx) = section_idx {
451 let section = ctx.path[section_idx].as_str();
452 let tree = ctx.path.get(section_idx + 1).cloned();
453 match (section, last.as_str()) {
454 ("eventTrees", "nodes") => {
455 ctx.kind = Some("node".to_string());
456 ctx.tree = tree;
457 }
458 ("faultTrees", "gates") => {
459 ctx.kind = Some("gate".to_string());
460 ctx.tree = tree;
461 }
462 ("faultTrees", "basicEvents") => {
463 ctx.kind = Some("basicEvent".to_string());
464 ctx.tree = tree;
465 }
466 _ => {}
467 }
468 }
469 }
470 maps.push(ctx);
471 containers.push(Container::Mapping);
472 }
473 Event::MappingEnd => {
474 maps.pop();
475 containers.pop();
476 }
477 Event::SequenceStart(..) => {
478 if let Some(parent) = maps.last_mut() {
479 parent.expect_key = true;
480 }
481 containers.push(Container::Sequence);
482 }
483 Event::SequenceEnd => {
484 containers.pop();
485 }
486 Event::Scalar(v, ..) => {
487 if containers.last() != Some(&Container::Mapping) {
488 continue;
489 }
490 let Some(ctx) = maps.last_mut() else { continue };
491 if ctx.expect_key {
492 let key = v.to_string();
493 if let (Some(kind), Some(tree)) = (ctx.kind.clone(), ctx.tree.clone()) {
494 if ctx.seen.contains_key(&key) {
495 duplicates.push(DuplicateId {
496 tree,
497 kind,
498 id: key.clone(),
499 span: line_map.span_of(span.start.index(), span.end.index()),
500 });
501 } else {
502 ctx.seen.insert(key.clone(), span.start.index());
503 }
504 }
505 ctx.path.push(key);
506 ctx.expect_key = false;
507 } else {
508 ctx.path.pop();
509 ctx.expect_key = true;
510 }
511 }
512 _ => {}
513 }
514 }
515
516 Ok(duplicates)
517}
518
519struct Builder<'a> {
524 content: &'a str,
525 index: SpanIndex,
526 line_map: LineMap<'a>,
527}
528
529impl<'a> Builder<'a> {
530 fn new(content: &'a str) -> Self {
531 Builder {
532 content,
533 index: SpanIndex::default(),
534 line_map: LineMap::new(content),
535 }
536 }
537
538 fn span(&self, start: usize, end: usize) -> Span {
539 self.line_map.span_of(start, end)
540 }
541
542 fn value_span(&self, node: &MarkedYaml) -> Span {
544 let (s, e) = byte_range(node);
545 let (ts, te) = token_span(self.content, s, e, &node_str(node));
546 self.span(ts, te)
547 }
548
549 fn add(&mut self, el: IndexedElement) {
550 let identity = match el.kind {
551 ElementKind::Definition | ElementKind::Reference => {
552 Some((el.tree.clone().unwrap_or_default(), el.name.clone()))
553 }
554 _ => None,
555 };
556 let idx = if let Some(&existing) = self.index.by_path.get(&el.path) {
557 self.index.elements[existing] = el;
558 existing
559 } else {
560 let idx = self.index.elements.len();
561 self.index.by_path.insert(el.path.clone(), idx);
562 self.index.elements.push(el);
563 idx
564 };
565 if let Some(id) = identity {
566 self.index.by_identity.entry(id).or_default().push(idx);
567 }
568 }
569}
570
571fn node_str(n: &MarkedYaml) -> String {
574 n.data.as_str().map(|s| s.to_string()).unwrap_or_default()
575}
576
577fn byte_range(n: &MarkedYaml) -> (usize, usize) {
578 (n.span.start.index(), n.span.end.index())
579}
580
581fn token_span(content: &str, start: usize, end: usize, value: &str) -> (usize, usize) {
584 let lo = start.min(content.len());
585 let hi = end.min(content.len());
586 let hay = &content[lo..hi];
587 if !value.is_empty() {
588 if let Some(rel) = hay.find(value) {
589 return (lo + rel, lo + rel + value.len());
590 }
591 }
592 (lo, hi)
593}
594
595fn out_name(key: &str) -> String {
597 match key {
598 "eventTrees" => "event_trees",
599 "faultTrees" => "fault_trees",
600 "basicEvents" => "basic_events",
601 "initiatingEvent" => "initiating_event",
602 "topEvent" => "top_event",
603 "onFailure" => "on_failure",
604 "onFailureProbabilitySource" => "on_failure_probability_source",
605 "probabilityOfSuccess" => "probability_of_success",
606 "probabilityOfFailure" => "probability_of_failure",
607 "probabilitySource" => "probability_source",
608 "rootCause" => "root_cause",
609 "inhibitCondition" => "inhibit_condition",
610 "retryPolicy" => "retry_policy",
611 "timeoutMs" => "timeout_ms",
612 "maxAttempts" => "max_attempts",
613 "backoffMs" => "backoff_ms",
614 "backoffStrategy" => "backoff_strategy",
615 "failureRate" => "failure_rate",
616 "missionTime" => "mission_time",
617 "eventType" => "event_type",
618 other => other,
619 }
620 .to_string()
621}
622
623impl<'a> Builder<'a> {
626 fn walk_root(&mut self, root: &MarkedYaml) {
627 let Some(map) = root.data.as_mapping() else {
628 return;
629 };
630 for (k, v) in map {
631 let key = node_str(k);
632 let out = out_name(&key);
633 let (ks, _ke) = byte_range(k);
634 let (_vs, ve) = byte_range(v);
635 let path = vec![PathPart::Key(out.clone())];
636 self.add(IndexedElement {
637 kind: ElementKind::Section,
638 name: out.clone(),
639 field: None,
640 tree: None,
641 span: self.span(ks, ve),
642 key_span: Some(self.span(ks, ks + key.len())),
643 path: path.clone(),
644 depth: 1,
645 });
646 match key.as_str() {
647 "info" => self.walk_info(v, &out),
648 "asyncapi_imports" => self.walk_imports(v, &out),
649 "components" => self.walk_components(v, &out),
650 "eventTrees" => self.walk_event_trees(v, &out),
651 "faultTrees" => self.walk_fault_trees(v, &out),
652 _ => {}
653 }
654 }
655 }
656
657 fn walk_info(&mut self, node: &MarkedYaml, base: &str) {
658 let Some(map) = node.data.as_mapping() else {
659 return;
660 };
661 for (k, v) in map {
662 let key = node_str(k);
663 let out = out_name(&key);
664 let (ks, _ke) = byte_range(k);
665 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(out.clone())];
666 self.add(IndexedElement {
667 kind: ElementKind::Field,
668 name: node_str(v),
669 field: Some(out.clone()),
670 tree: None,
671 span: self.span(ks, byte_range(v).1),
672 key_span: Some(self.span(ks, ks + key.len())),
673 path,
674 depth: 2,
675 });
676 }
677 }
678
679 fn walk_imports(&mut self, node: &MarkedYaml, base: &str) {
680 let Some(map) = node.data.as_mapping() else {
681 return;
682 };
683 for (k, v) in map {
684 let alias = node_str(k);
685 let (ks, _ke) = byte_range(k);
686 let path = vec![
687 PathPart::Key(base.to_string()),
688 PathPart::Key(alias.clone()),
689 ];
690 self.add(IndexedElement {
691 kind: ElementKind::Field,
692 name: alias.clone(),
693 field: Some(alias.clone()),
694 tree: None,
695 span: self.span(ks, byte_range(v).1),
696 key_span: Some(self.span(ks, ks + alias.len())),
697 path,
698 depth: 2,
699 });
700 }
701 }
702
703 fn walk_event_trees(&mut self, node: &MarkedYaml, base: &str) {
704 let Some(map) = node.data.as_mapping() else {
705 return;
706 };
707 for (tk, tv) in map {
708 let tree = node_str(tk);
709 let (ks, _ke) = byte_range(tk);
710 let (_, ve) = byte_range(tv);
711 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(tree.clone())];
712 self.add(IndexedElement {
713 kind: ElementKind::Definition,
714 name: tree.clone(),
715 field: None,
716 tree: Some(tree.clone()),
717 span: self.span(ks, ve),
718 key_span: Some(self.span(ks, ks + tree.len())),
719 path: path.clone(),
720 depth: 2,
721 });
722 self.walk_event_tree_fields(tv, base, &tree, &path);
723 }
724 }
725
726 fn walk_event_tree_fields(
727 &mut self,
728 node: &MarkedYaml,
729 base: &str,
730 tree: &str,
731 tree_path: &PathKey,
732 ) {
733 let Some(map) = node.data.as_mapping() else {
734 return;
735 };
736 for (k, v) in map {
737 let key = node_str(k);
738 let (ks, _ke) = byte_range(k);
739 let (_, ve) = byte_range(v);
740 let mut path = tree_path.clone();
741 match key.as_str() {
742 "initiatingEvent" => {
743 path.push(PathPart::Key("initiating_event".to_string()));
744 self.add(IndexedElement {
745 kind: ElementKind::Field,
746 name: "initiatingEvent".to_string(),
747 field: Some("initiating_event".to_string()),
748 tree: Some(tree.to_string()),
749 span: self.span(ks, ve),
750 key_span: Some(self.span(ks, ks + key.len())),
751 path: path.clone(),
752 depth: 3,
753 });
754 self.walk_initiating_event(v, tree, &path);
755 }
756 "nodes" => {
757 path.push(PathPart::Key("nodes".to_string()));
758 self.add(IndexedElement {
759 kind: ElementKind::Field,
760 name: "nodes".to_string(),
761 field: Some("nodes".to_string()),
762 tree: Some(tree.to_string()),
763 span: self.span(ks, ve),
764 key_span: Some(self.span(ks, ks + key.len())),
765 path: path.clone(),
766 depth: 3,
767 });
768 self.walk_nodes(v, base, tree, &path);
769 }
770 "description" => {
771 path.push(PathPart::Key("description".to_string()));
772 self.add(IndexedElement {
773 kind: ElementKind::Field,
774 name: node_str(v),
775 field: Some("description".to_string()),
776 tree: Some(tree.to_string()),
777 span: self.span(ks, ve),
778 key_span: Some(self.span(ks, ks + key.len())),
779 path,
780 depth: 3,
781 });
782 }
783 _ => {}
784 }
785 }
786 }
787
788 fn walk_initiating_event(&mut self, node: &MarkedYaml, tree: &str, base_path: &PathKey) {
789 self.walk_scalar_map(
790 node,
791 tree,
792 base_path,
793 &[("id", None), ("message", Some(true)), ("next", Some(true))],
794 );
795 }
796
797 fn walk_nodes(&mut self, node: &MarkedYaml, base: &str, tree: &str, nodes_path: &PathKey) {
798 let Some(map) = node.data.as_mapping() else {
799 return;
800 };
801 for (nk, nv) in map {
802 let nid = node_str(nk);
803 let (ks, _ke) = byte_range(nk);
804 let (_, ve) = byte_range(nv);
805 let mut path = nodes_path.clone();
806 path.push(PathPart::Key(nid.clone()));
807 self.add(IndexedElement {
808 kind: ElementKind::Definition,
809 name: nid.clone(),
810 field: None,
811 tree: Some(tree.to_string()),
812 span: self.span(ks, ve),
813 key_span: Some(self.span(ks, ks + nid.len())),
814 path: path.clone(),
815 depth: 4,
816 });
817 let Some(fields) = nv.data.as_mapping() else {
818 continue;
819 };
820 for (k, v) in fields {
821 let key = node_str(k);
822 let (fks, _fke) = byte_range(k);
823 let mut fpath = path.clone();
824 let field = out_name(&key);
825 fpath.push(PathPart::Key(field.clone()));
826 let is_ref = matches!(
827 key.as_str(),
828 "next"
829 | "onFailure"
830 | "onFailureProbabilitySource"
831 | "emits"
832 | "channel"
833 | "message"
834 );
835 if is_ref {
836 self.add(IndexedElement {
837 kind: ElementKind::Reference,
838 name: node_str(v),
839 field: Some(field.clone()),
840 tree: Some(tree.to_string()),
841 span: self.value_span(v),
842 key_span: None,
843 path: fpath,
844 depth: 5,
845 });
846 } else if key == "branches" {
847 self.add(IndexedElement {
848 kind: ElementKind::Field,
849 name: "branches".to_string(),
850 field: Some("branches".to_string()),
851 tree: Some(tree.to_string()),
852 span: self.span(fks, byte_range(v).1),
853 key_span: Some(self.span(fks, fks + key.len())),
854 path: fpath.clone(),
855 depth: 5,
856 });
857 self.walk_branches(v, tree, &fpath);
858 } else {
859 self.add(IndexedElement {
860 kind: ElementKind::Field,
861 name: node_str(v),
862 field: Some(field.clone()),
863 tree: Some(tree.to_string()),
864 span: self.span(fks, byte_range(v).1),
865 key_span: Some(self.span(fks, fks + key.len())),
866 path: fpath,
867 depth: 5,
868 });
869 }
870 }
871 let _ = base;
872 }
873 }
874
875 fn walk_branches(&mut self, node: &MarkedYaml, tree: &str, branches_path: &PathKey) {
876 let Some(seq) = node.data.as_vec() else {
877 return;
878 };
879 for (i, bv) in seq.iter().enumerate() {
880 let (bs, be) = byte_range(bv);
881 let mut bpath = branches_path.clone();
882 bpath.push(PathPart::Index(i));
883 self.add(IndexedElement {
884 kind: ElementKind::Field,
885 name: format!("branches[{}]", i),
886 field: Some("branches".to_string()),
887 tree: Some(tree.to_string()),
888 span: self.span(bs, be),
889 key_span: None,
890 path: bpath.clone(),
891 depth: 6,
892 });
893 let Some(map) = bv.data.as_mapping() else {
894 continue;
895 };
896 for (k, v) in map {
897 let key = node_str(k);
898 let (ks, _ke) = byte_range(k);
899 let mut fpath = bpath.clone();
900 let field = out_name(&key);
901 fpath.push(PathPart::Key(field.clone()));
902 let is_ref = matches!(key.as_str(), "next" | "probabilitySource");
903 let kind = if is_ref {
904 ElementKind::Reference
905 } else {
906 ElementKind::Field
907 };
908 self.add(IndexedElement {
909 kind,
910 name: node_str(v),
911 field: Some(field.clone()),
912 tree: Some(tree.to_string()),
913 span: if is_ref {
914 self.value_span(v)
915 } else {
916 self.span(ks, byte_range(v).1)
917 },
918 key_span: None,
919 path: fpath,
920 depth: 7,
921 });
922 }
923 }
924 }
925
926 fn walk_scalar_map(
929 &mut self,
930 node: &MarkedYaml,
931 tree: &str,
932 base_path: &PathKey,
933 spec: &[(&str, Option<bool>)],
934 ) {
935 let Some(map) = node.data.as_mapping() else {
936 return;
937 };
938 for (k, v) in map {
939 let key = node_str(k);
940 let field = out_name(&key);
941 if let Some((_, is_ref)) = spec.iter().find(|(f, _)| *f == key.as_str()) {
942 let (ks, _ke) = byte_range(k);
943 let mut path = base_path.clone();
944 path.push(PathPart::Key(field.clone()));
945 let kind = if is_ref.unwrap_or(false) {
946 ElementKind::Reference
947 } else {
948 ElementKind::Definition
949 };
950 self.add(IndexedElement {
951 kind,
952 name: node_str(v),
953 field: Some(field.clone()),
954 tree: Some(tree.to_string()),
955 span: if kind == ElementKind::Reference {
956 self.value_span(v)
957 } else {
958 self.span(ks, byte_range(v).1)
959 },
960 key_span: None,
961 path,
962 depth: 4,
963 });
964 }
965 }
966 }
967
968 fn walk_fault_trees(&mut self, node: &MarkedYaml, base: &str) {
969 let Some(map) = node.data.as_mapping() else {
970 return;
971 };
972 for (tk, tv) in map {
973 let tree = node_str(tk);
974 let (ks, _ke) = byte_range(tk);
975 let (_, ve) = byte_range(tv);
976 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(tree.clone())];
977 self.add(IndexedElement {
978 kind: ElementKind::Definition,
979 name: tree.clone(),
980 field: None,
981 tree: Some(tree.clone()),
982 span: self.span(ks, ve),
983 key_span: Some(self.span(ks, ks + tree.len())),
984 path: path.clone(),
985 depth: 2,
986 });
987 self.walk_fault_tree_fields(tv, base, &tree, &path);
988 }
989 }
990
991 fn walk_fault_tree_fields(
992 &mut self,
993 node: &MarkedYaml,
994 base: &str,
995 tree: &str,
996 tree_path: &PathKey,
997 ) {
998 let Some(map) = node.data.as_mapping() else {
999 return;
1000 };
1001 for (k, v) in map {
1002 let key = node_str(k);
1003 let (ks, _ke) = byte_range(k);
1004 let (_, ve) = byte_range(v);
1005 let mut path = tree_path.clone();
1006 match key.as_str() {
1007 "topEvent" => {
1008 path.push(PathPart::Key("top_event".to_string()));
1009 self.add(IndexedElement {
1010 kind: ElementKind::Field,
1011 name: "topEvent".to_string(),
1012 field: Some("top_event".to_string()),
1013 tree: Some(tree.to_string()),
1014 span: self.span(ks, ve),
1015 key_span: Some(self.span(ks, ks + key.len())),
1016 path: path.clone(),
1017 depth: 3,
1018 });
1019 self.walk_top_event(v, tree, &path);
1020 }
1021 "gates" => {
1022 path.push(PathPart::Key("gates".to_string()));
1023 self.add(IndexedElement {
1024 kind: ElementKind::Field,
1025 name: "gates".to_string(),
1026 field: Some("gates".to_string()),
1027 tree: Some(tree.to_string()),
1028 span: self.span(ks, ve),
1029 key_span: Some(self.span(ks, ks + key.len())),
1030 path: path.clone(),
1031 depth: 3,
1032 });
1033 self.walk_gates(v, base, tree, &path);
1034 }
1035 "basicEvents" => {
1036 let field = "basic_events";
1037 path.push(PathPart::Key(field.to_string()));
1038 self.add(IndexedElement {
1039 kind: ElementKind::Field,
1040 name: "basicEvents".to_string(),
1041 field: Some(field.to_string()),
1042 tree: Some(tree.to_string()),
1043 span: self.span(ks, ve),
1044 key_span: Some(self.span(ks, ks + key.len())),
1045 path: path.clone(),
1046 depth: 3,
1047 });
1048 self.walk_basic_events(v, base, tree, &path);
1049 }
1050 "transfers" => {
1051 path.push(PathPart::Key("transfers".to_string()));
1052 self.add(IndexedElement {
1053 kind: ElementKind::Field,
1054 name: "transfers".to_string(),
1055 field: Some("transfers".to_string()),
1056 tree: Some(tree.to_string()),
1057 span: self.span(ks, ve),
1058 key_span: Some(self.span(ks, ks + key.len())),
1059 path: path.clone(),
1060 depth: 3,
1061 });
1062 self.walk_transfers(v, base, tree, &path);
1063 }
1064 "description" => {
1065 path.push(PathPart::Key("description".to_string()));
1066 self.add(IndexedElement {
1067 kind: ElementKind::Field,
1068 name: node_str(v),
1069 field: Some("description".to_string()),
1070 tree: Some(tree.to_string()),
1071 span: self.span(ks, ve),
1072 key_span: Some(self.span(ks, ks + key.len())),
1073 path,
1074 depth: 3,
1075 });
1076 }
1077 _ => {}
1078 }
1079 }
1080 }
1081
1082 fn walk_top_event(&mut self, node: &MarkedYaml, tree: &str, base_path: &PathKey) {
1083 let Some(map) = node.data.as_mapping() else {
1084 return;
1085 };
1086 for (k, v) in map {
1087 let key = node_str(k);
1088 let (ks, _ke) = byte_range(k);
1089 let field = out_name(&key);
1090 let mut path = base_path.clone();
1091 path.push(PathPart::Key(field.clone()));
1092 let is_ref = matches!(key.as_str(), "message" | "rootCause");
1093 let kind = if is_ref {
1094 ElementKind::Reference
1095 } else {
1096 ElementKind::Field
1097 };
1098 self.add(IndexedElement {
1099 kind,
1100 name: node_str(v),
1101 field: Some(field.clone()),
1102 tree: Some(tree.to_string()),
1103 span: if is_ref {
1104 self.value_span(v)
1105 } else {
1106 self.span(ks, byte_range(v).1)
1107 },
1108 key_span: None,
1109 path,
1110 depth: 4,
1111 });
1112 }
1113 }
1114
1115 fn walk_gates(&mut self, node: &MarkedYaml, base: &str, tree: &str, gates_path: &PathKey) {
1116 let Some(map) = node.data.as_mapping() else {
1117 return;
1118 };
1119 for (gk, gv) in map {
1120 let gid = node_str(gk);
1121 let (ks, _ke) = byte_range(gk);
1122 let (_, ve) = byte_range(gv);
1123 let mut path = gates_path.clone();
1124 path.push(PathPart::Key(gid.clone()));
1125 self.add(IndexedElement {
1126 kind: ElementKind::Definition,
1127 name: gid.clone(),
1128 field: None,
1129 tree: Some(tree.to_string()),
1130 span: self.span(ks, ve),
1131 key_span: Some(self.span(ks, ks + gid.len())),
1132 path: path.clone(),
1133 depth: 4,
1134 });
1135 let Some(fields) = gv.data.as_mapping() else {
1136 continue;
1137 };
1138 for (k, v) in fields {
1139 let key = node_str(k);
1140 let (fks, _fke) = byte_range(k);
1141 let field = out_name(&key);
1142 let mut fpath = path.clone();
1143 fpath.push(PathPart::Key(field.clone()));
1144 match key.as_str() {
1145 "inputs" => {
1146 self.add(IndexedElement {
1147 kind: ElementKind::Field,
1148 name: node_str(v),
1149 field: Some("inputs".to_string()),
1150 tree: Some(tree.to_string()),
1151 span: self.span(fks, byte_range(v).1),
1152 key_span: Some(self.span(fks, fks + key.len())),
1153 path: fpath.clone(),
1154 depth: 5,
1155 });
1156 if let Some(seq) = v.data.as_vec() {
1157 for (i, item) in seq.iter().enumerate() {
1158 let mut ipath = fpath.clone();
1159 ipath.push(PathPart::Index(i));
1160 self.add(IndexedElement {
1161 kind: ElementKind::Reference,
1162 name: node_str(item),
1163 field: Some("inputs".to_string()),
1164 tree: Some(tree.to_string()),
1165 span: self.value_span(item),
1166 key_span: None,
1167 path: ipath,
1168 depth: 6,
1169 });
1170 }
1171 }
1172 }
1173 _ => {
1174 self.add(IndexedElement {
1175 kind: ElementKind::Field,
1176 name: node_str(v),
1177 field: Some(field.clone()),
1178 tree: Some(tree.to_string()),
1179 span: self.span(fks, byte_range(v).1),
1180 key_span: Some(self.span(fks, fks + key.len())),
1181 path: fpath,
1182 depth: 5,
1183 });
1184 }
1185 }
1186 }
1187 let _ = base;
1188 }
1189 }
1190
1191 fn walk_basic_events(
1192 &mut self,
1193 node: &MarkedYaml,
1194 base: &str,
1195 tree: &str,
1196 events_path: &PathKey,
1197 ) {
1198 let Some(map) = node.data.as_mapping() else {
1199 return;
1200 };
1201 for (ek, ev) in map {
1202 let eid = node_str(ek);
1203 let (ks, _ke) = byte_range(ek);
1204 let (_, ve) = byte_range(ev);
1205 let mut path = events_path.clone();
1206 path.push(PathPart::Key(eid.clone()));
1207 self.add(IndexedElement {
1208 kind: ElementKind::Definition,
1209 name: eid.clone(),
1210 field: None,
1211 tree: Some(tree.to_string()),
1212 span: self.span(ks, ve),
1213 key_span: Some(self.span(ks, ks + eid.len())),
1214 path: path.clone(),
1215 depth: 4,
1216 });
1217 let Some(fields) = ev.data.as_mapping() else {
1218 continue;
1219 };
1220 for (k, v) in fields {
1221 let key = node_str(k);
1222 let (fks, _fke) = byte_range(k);
1223 let field = out_name(&key);
1224 let mut fpath = path.clone();
1225 fpath.push(PathPart::Key(field.clone()));
1226 let is_ref = key == "message";
1227 let kind = if is_ref {
1228 ElementKind::Reference
1229 } else {
1230 ElementKind::Field
1231 };
1232 self.add(IndexedElement {
1233 kind,
1234 name: node_str(v),
1235 field: Some(field.clone()),
1236 tree: Some(tree.to_string()),
1237 span: if is_ref {
1238 self.value_span(v)
1239 } else {
1240 self.span(fks, byte_range(v).1)
1241 },
1242 key_span: None,
1243 path: fpath,
1244 depth: 5,
1245 });
1246 }
1247 let _ = base;
1248 }
1249 }
1250
1251 fn walk_transfers(
1252 &mut self,
1253 node: &MarkedYaml,
1254 base: &str,
1255 tree: &str,
1256 transfers_path: &PathKey,
1257 ) {
1258 let Some(map) = node.data.as_mapping() else {
1259 return;
1260 };
1261 for (tk, tv) in map {
1262 let tid = node_str(tk);
1263 let (ks, _ke) = byte_range(tk);
1264 let (_, ve) = byte_range(tv);
1265 let mut path = transfers_path.clone();
1266 path.push(PathPart::Key(tid.clone()));
1267 self.add(IndexedElement {
1268 kind: ElementKind::Definition,
1269 name: tid.clone(),
1270 field: None,
1271 tree: Some(tree.to_string()),
1272 span: self.span(ks, ve),
1273 key_span: Some(self.span(ks, ks + tid.len())),
1274 path: path.clone(),
1275 depth: 4,
1276 });
1277 let Some(fields) = tv.data.as_mapping() else {
1278 continue;
1279 };
1280 for (k, v) in fields {
1281 let key = node_str(k);
1282 let (fks, _fke) = byte_range(k);
1283 let field = out_name(&key);
1284 let mut fpath = path.clone();
1285 fpath.push(PathPart::Key(field.clone()));
1286 self.add(IndexedElement {
1287 kind: ElementKind::Field,
1288 name: node_str(v),
1289 field: Some(field.clone()),
1290 tree: Some(tree.to_string()),
1291 span: self.span(fks, byte_range(v).1),
1292 key_span: None,
1293 path: fpath,
1294 depth: 5,
1295 });
1296 }
1297 let _ = base;
1298 }
1299 }
1300
1301 fn walk_components(&mut self, node: &MarkedYaml, base: &str) {
1302 let Some(map) = node.data.as_mapping() else {
1303 return;
1304 };
1305 for (k, v) in map {
1306 let key = node_str(k);
1307 let (ks, _ke) = byte_range(k);
1308 let field = out_name(&key);
1309 let path = vec![
1310 PathPart::Key(base.to_string()),
1311 PathPart::Key(field.clone()),
1312 ];
1313 self.add(IndexedElement {
1314 kind: ElementKind::Field,
1315 name: key.clone(),
1316 field: Some(field.clone()),
1317 tree: None,
1318 span: self.span(ks, byte_range(v).1),
1319 key_span: Some(self.span(ks, ks + key.len())),
1320 path: path.clone(),
1321 depth: 2,
1322 });
1323 let Some(items) = v.data.as_mapping() else {
1324 continue;
1325 };
1326 for (ik, iv) in items {
1327 let iid = node_str(ik);
1328 let (iks, _ike) = byte_range(ik);
1329 let mut ipath = path.clone();
1330 ipath.push(PathPart::Key(iid.clone()));
1331 self.add(IndexedElement {
1332 kind: ElementKind::Definition,
1333 name: iid.clone(),
1334 field: None,
1335 tree: Some(iid.clone()),
1336 span: self.span(iks, byte_range(iv).1),
1337 key_span: Some(self.span(iks, iks + iid.len())),
1338 path: ipath.clone(),
1339 depth: 3,
1340 });
1341 let Some(fields) = iv.data.as_mapping() else {
1342 continue;
1343 };
1344 for (fk, fv) in fields {
1345 let fkey = node_str(fk);
1346 let (fks, _fke) = byte_range(fk);
1347 let fname = out_name(&fkey);
1348 let mut fpath = ipath.clone();
1349 fpath.push(PathPart::Key(fname.clone()));
1350 let is_ref = matches!(
1351 fkey.as_str(),
1352 "next" | "onFailure" | "message" | "channel" | "emits" | "inputs"
1353 );
1354 let kind = if is_ref {
1355 ElementKind::Reference
1356 } else {
1357 ElementKind::Field
1358 };
1359 self.add(IndexedElement {
1360 kind,
1361 name: node_str(fv),
1362 field: Some(fname.clone()),
1363 tree: Some(iid.clone()),
1364 span: if is_ref {
1365 self.value_span(fv)
1366 } else {
1367 self.span(fks, byte_range(fv).1)
1368 },
1369 key_span: None,
1370 path: fpath,
1371 depth: 4,
1372 });
1373 }
1374 }
1375 }
1376 }
1377}
1378
1379struct LineMap<'a> {
1384 content: &'a str,
1385 line_starts: Vec<usize>,
1386}
1387
1388impl<'a> LineMap<'a> {
1389 fn new(content: &'a str) -> Self {
1390 let mut line_starts = vec![0];
1391 for (i, b) in content.bytes().enumerate() {
1392 if b == b'\n' {
1393 line_starts.push(i + 1);
1394 }
1395 }
1396 LineMap {
1397 content,
1398 line_starts,
1399 }
1400 }
1401
1402 fn line_of(&self, byte: usize) -> usize {
1403 match self.line_starts.binary_search(&byte) {
1404 Ok(i) => i,
1405 Err(i) => i.saturating_sub(1),
1406 }
1407 .min(self.line_starts.len().saturating_sub(1))
1408 }
1409
1410 fn char_offset(&self, byte: usize) -> usize {
1411 let byte = byte.min(self.content.len());
1412 let byte = self.nearest_char_boundary(byte);
1415 self.content[..byte].chars().count()
1416 }
1417
1418 fn nearest_char_boundary(&self, byte: usize) -> usize {
1420 let byte = byte.min(self.content.len());
1421 let bytes = self.content.as_bytes();
1422 let mut b = byte;
1423 while b > 0 && b < bytes.len() && (bytes[b] & 0xC0) == 0x80 {
1426 b -= 1;
1427 }
1428 b
1429 }
1430
1431 fn span_of(&self, start: usize, end: usize) -> Span {
1432 let start = self.nearest_char_boundary(start);
1433 let end = self.nearest_char_boundary(end);
1434 let line = self.line_of(start);
1435 let line_start = self.nearest_char_boundary(self.line_starts[line]);
1436 let line_start = line_start.min(start);
1437 let column = self.content[line_start..start].chars().count();
1438 let end_line = self.line_of(end);
1439 let end_line_start = self.nearest_char_boundary(self.line_starts[end_line]);
1440 let end_line_start = end_line_start.min(end);
1441 let end_column = self.content[end_line_start..end].chars().count();
1442 Span {
1443 start: self.char_offset(start) as u32,
1444 end: self.char_offset(end) as u32,
1445 line: line as u32,
1446 column: column as u32,
1447 end_line: end_line as u32,
1448 end_column: end_column as u32,
1449 }
1450 }
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::*;
1456 use serde_json::json;
1457
1458 const FIXTURE: &str = include_str!("../tests/fixtures/order-fulfillment.etdl");
1459
1460 #[test]
1461 fn index_covers_sections_and_definitions() {
1462 let index = build_span_index(FIXTURE).unwrap();
1463 let event_trees = index
1464 .resolve(&SpanKey::Section("event_trees"))
1465 .expect("event_trees section");
1466 assert_eq!(event_trees.kind, ElementKind::Section);
1467 assert_eq!(event_trees.span.line, 11); let tree = index
1470 .resolve(&SpanKey::Tree {
1471 tree: "OrderFulfillment".to_string(),
1472 })
1473 .expect("tree definition");
1474 assert_eq!(tree.kind, ElementKind::Definition);
1475 assert_eq!(tree.span.line, 12);
1476
1477 let node = index
1478 .resolve(&SpanKey::Node {
1479 tree: "OrderFulfillment".to_string(),
1480 id: "InventoryCheckBarrier".to_string(),
1481 })
1482 .expect("node definition");
1483 assert_eq!(node.span.line, 20);
1484 let key_span = node.key_span.expect("key span");
1485 assert_eq!(key_span.line, 20);
1486 assert_eq!(key_span.column, 6); }
1488
1489 #[test]
1490 fn index_covers_references() {
1491 let index = build_span_index(FIXTURE).unwrap();
1492 let next = index
1493 .resolve(&SpanKey::NodeField {
1494 tree: "OrderFulfillment".to_string(),
1495 id: "ProcessPaymentOperation".to_string(),
1496 field: "next",
1497 })
1498 .expect("next reference");
1499 assert_eq!(next.kind, ElementKind::Reference);
1500 assert_eq!(next.name, "FulfillmentConsequence");
1501 assert_eq!(next.span.line, 39);
1502
1503 let message = index
1504 .resolve(&SpanKey::InitiatingEvent {
1505 tree: "OrderFulfillment".to_string(),
1506 field: "message",
1507 })
1508 .expect("initiatingEvent.message reference");
1509 assert_eq!(message.kind, ElementKind::Reference);
1510 assert_eq!(message.name, "orders_api#/components/messages/OrderPlaced");
1511
1512 let gate_input = index
1513 .resolve(&SpanKey::GateInput {
1514 tree: "PaymentGatewayFailure".to_string(),
1515 id: "GatewayUnavailableOrRejected".to_string(),
1516 idx: 1,
1517 })
1518 .expect("gate input reference");
1519 assert_eq!(gate_input.name, "ChargeRejected");
1520
1521 let root_cause = index
1522 .resolve(&SpanKey::TopEvent {
1523 tree: "PaymentGatewayFailure".to_string(),
1524 field: "root_cause",
1525 })
1526 .expect("rootCause reference");
1527 assert_eq!(root_cause.name, "GatewayUnavailableOrRejected");
1528 }
1529
1530 #[test]
1531 fn find_deepest_resolves_reference_tokens() {
1532 let index = build_span_index(FIXTURE).unwrap();
1533 let line = FIXTURE.lines().nth(39).unwrap();
1535 let byte_offset = FIXTURE.lines().take(39).map(|l| l.len() + 1).sum::<usize>()
1536 + line.find("FulfillmentConsequence").unwrap();
1537 let char_offset = FIXTURE[..byte_offset].chars().count() as u32;
1538
1539 let el = index.find_deepest(char_offset).expect("found");
1540 assert_eq!(el.kind, ElementKind::Reference);
1541 assert_eq!(el.name, "FulfillmentConsequence");
1542 assert_eq!(el.field.as_deref(), Some("next"));
1543 assert_eq!(el.tree.as_deref(), Some("OrderFulfillment"));
1544 assert!(el.span.start <= char_offset && char_offset < el.span.end);
1545 }
1546
1547 #[test]
1548 fn inject_spans_wraps_scalars() {
1549 let (doc, index) = parse_document_with_spans(FIXTURE).unwrap();
1550 let mut value = serde_json::to_value(&doc).unwrap();
1551 inject_spans(&mut value, &index);
1552
1553 let event_trees = value.get("event_trees").expect("event_trees");
1554 assert!(event_trees.get("span").is_some(), "section span attached");
1555 let tree = &event_trees["OrderFulfillment"];
1556 assert!(tree.get("span").is_some());
1557 let node = &tree["nodes"]["InventoryCheckBarrier"];
1558 assert!(node.get("span").is_some(), "node block span attached");
1559
1560 let op = &tree["nodes"]["ProcessPaymentOperation"];
1562 let next = &op["next"];
1563 assert!(next.is_object());
1564 assert_eq!(next["value"], "FulfillmentConsequence");
1565 assert!(next.get("span").is_some());
1566
1567 assert_eq!(op["retry_policy"]["max_attempts"], 3);
1569 assert_eq!(op["action"]["value"], "execute");
1570 assert!(op["action"].get("span").is_some());
1571 }
1572
1573 #[test]
1574 fn duplicate_ids_are_detected() {
1575 let yaml = r#"
1576etdl: "1.0.0"
1577info:
1578 title: "T"
1579 version: "1.0.0"
1580 domain: "D"
1581asyncapi_imports: {}
1582eventTrees:
1583 T:
1584 initiatingEvent:
1585 id: I
1586 message: "a#/m"
1587 next: N
1588 nodes:
1589 N:
1590 type: barrier
1591 branches:
1592 - outcome: ok
1593 condition: "default"
1594 next: M
1595 M:
1596 type: consequence
1597 operation: terminate
1598 N:
1599 type: consequence
1600 operation: terminate
1601"#;
1602 let dups = detect_duplicate_ids(yaml).unwrap();
1603 assert_eq!(dups.len(), 1, "expected one duplicate node id");
1604 assert_eq!(dups[0].kind, "node");
1605 assert_eq!(dups[0].id, "N");
1606 assert_eq!(dups[0].tree, "T");
1607 }
1608
1609 #[test]
1610 fn span_key_resolves_to_path() {
1611 let index = build_span_index(FIXTURE).unwrap();
1612 let branch = index
1613 .resolve(&SpanKey::BranchField {
1614 tree: "OrderFulfillment".to_string(),
1615 id: "InventoryCheckBarrier".to_string(),
1616 branch: 1,
1617 field: "next",
1618 })
1619 .expect("branch next reference");
1620 assert_eq!(branch.name, "OutOfStockConsequence");
1621 let _ = json!({});
1622 }
1623}