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 docs = MarkedYaml::load_from_str(content).map_err(|e| e.to_string())?;
343 let root = docs.first().ok_or("empty ETDL document")?;
344 let mut builder = Builder::new(content);
345 builder.walk_root(root);
346 Ok(builder.index)
347}
348
349pub fn inject_spans(value: &mut Value, index: &SpanIndex) {
352 let mut path: PathKey = Vec::new();
353 walk_inject(value, index, &mut path);
354}
355
356fn walk_inject(value: &mut Value, index: &SpanIndex, path: &mut PathKey) {
357 if let Some(&idx) = index.by_path.get(path) {
358 let el = &index.elements[idx];
359 let span = serde_json::to_value(el.span).unwrap_or_default();
360 match value {
361 Value::Object(map) => {
362 map.insert("span".to_string(), span);
363 }
364 Value::Array(_) => {
365 }
367 _ => {
368 let inner = std::mem::replace(value, Value::Null);
369 let mut map = serde_json::Map::new();
370 map.insert("value".to_string(), inner);
371 map.insert("span".to_string(), span);
372 *value = Value::Object(map);
373 }
374 }
375 }
376 match value {
377 Value::Object(map) => {
378 for (k, v) in map.iter_mut() {
379 path.push(PathPart::Key(k.clone()));
380 walk_inject(v, index, path);
381 path.pop();
382 }
383 }
384 Value::Array(arr) => {
385 for (i, v) in arr.iter_mut().enumerate() {
386 path.push(PathPart::Index(i));
387 walk_inject(v, index, path);
388 path.pop();
389 }
390 }
391 _ => {}
392 }
393}
394
395pub fn detect_duplicate_ids(content: &str) -> Result<Vec<DuplicateId>, String> {
399 use saphyr_parser::{Event, Parser};
400
401 #[derive(Clone, Copy, PartialEq)]
402 enum Container {
403 Mapping,
404 Sequence,
405 }
406
407 #[derive(Default)]
408 struct MapCtx {
409 expect_key: bool,
410 seen: std::collections::BTreeMap<String, usize>,
411 path: Vec<String>,
412 kind: Option<String>,
413 tree: Option<String>,
414 }
415
416 let mut parser = Parser::new_from_str(content);
417 let mut duplicates = Vec::new();
418 let line_map = LineMap::new(content);
419 let mut maps: Vec<MapCtx> = Vec::new();
420 let mut containers: Vec<Container> = Vec::new();
421
422 while let Some(res) = parser.next_event() {
423 let (ev, span) = res.map_err(|e| e.to_string())?;
424 match ev {
425 Event::MappingStart(..) => {
426 if let Some(parent) = maps.last_mut() {
427 parent.expect_key = true;
429 }
430 let parent_path = maps.last().map(|c| c.path.clone()).unwrap_or_default();
431 let mut ctx = MapCtx {
432 expect_key: true,
433 path: parent_path,
434 ..Default::default()
435 };
436 if let Some(last) = ctx.path.last() {
437 let section_idx = ctx
438 .path
439 .iter()
440 .position(|p| p == "eventTrees" || p == "faultTrees");
441 if let Some(section_idx) = section_idx {
442 let section = ctx.path[section_idx].as_str();
443 let tree = ctx.path.get(section_idx + 1).cloned();
444 match (section, last.as_str()) {
445 ("eventTrees", "nodes") => {
446 ctx.kind = Some("node".to_string());
447 ctx.tree = tree;
448 }
449 ("faultTrees", "gates") => {
450 ctx.kind = Some("gate".to_string());
451 ctx.tree = tree;
452 }
453 ("faultTrees", "basicEvents") => {
454 ctx.kind = Some("basicEvent".to_string());
455 ctx.tree = tree;
456 }
457 _ => {}
458 }
459 }
460 }
461 maps.push(ctx);
462 containers.push(Container::Mapping);
463 }
464 Event::MappingEnd => {
465 maps.pop();
466 containers.pop();
467 }
468 Event::SequenceStart(..) => {
469 if let Some(parent) = maps.last_mut() {
470 parent.expect_key = true;
471 }
472 containers.push(Container::Sequence);
473 }
474 Event::SequenceEnd => {
475 containers.pop();
476 }
477 Event::Scalar(v, ..) => {
478 if containers.last() != Some(&Container::Mapping) {
479 continue;
480 }
481 let Some(ctx) = maps.last_mut() else { continue };
482 if ctx.expect_key {
483 let key = v.to_string();
484 if let (Some(kind), Some(tree)) = (ctx.kind.clone(), ctx.tree.clone()) {
485 if ctx.seen.contains_key(&key) {
486 duplicates.push(DuplicateId {
487 tree,
488 kind,
489 id: key.clone(),
490 span: line_map.span_of(span.start.index(), span.end.index()),
491 });
492 } else {
493 ctx.seen.insert(key.clone(), span.start.index());
494 }
495 }
496 ctx.path.push(key);
497 ctx.expect_key = false;
498 } else {
499 ctx.path.pop();
500 ctx.expect_key = true;
501 }
502 }
503 _ => {}
504 }
505 }
506
507 Ok(duplicates)
508}
509
510struct Builder<'a> {
515 content: &'a str,
516 index: SpanIndex,
517 line_map: LineMap<'a>,
518}
519
520impl<'a> Builder<'a> {
521 fn new(content: &'a str) -> Self {
522 Builder {
523 content,
524 index: SpanIndex::default(),
525 line_map: LineMap::new(content),
526 }
527 }
528
529 fn span(&self, start: usize, end: usize) -> Span {
530 self.line_map.span_of(start, end)
531 }
532
533 fn value_span(&self, node: &MarkedYaml) -> Span {
535 let (s, e) = byte_range(node);
536 let (ts, te) = token_span(self.content, s, e, &node_str(node));
537 self.span(ts, te)
538 }
539
540 fn add(&mut self, el: IndexedElement) {
541 let identity = match el.kind {
542 ElementKind::Definition | ElementKind::Reference => {
543 Some((el.tree.clone().unwrap_or_default(), el.name.clone()))
544 }
545 _ => None,
546 };
547 let idx = if let Some(&existing) = self.index.by_path.get(&el.path) {
548 self.index.elements[existing] = el;
549 existing
550 } else {
551 let idx = self.index.elements.len();
552 self.index.by_path.insert(el.path.clone(), idx);
553 self.index.elements.push(el);
554 idx
555 };
556 if let Some(id) = identity {
557 self.index.by_identity.entry(id).or_default().push(idx);
558 }
559 }
560}
561
562fn node_str(n: &MarkedYaml) -> String {
565 n.data.as_str().map(|s| s.to_string()).unwrap_or_default()
566}
567
568fn byte_range(n: &MarkedYaml) -> (usize, usize) {
569 (n.span.start.index(), n.span.end.index())
570}
571
572fn token_span(content: &str, start: usize, end: usize, value: &str) -> (usize, usize) {
575 let lo = start.min(content.len());
576 let hi = end.min(content.len());
577 let hay = &content[lo..hi];
578 if !value.is_empty() {
579 if let Some(rel) = hay.find(value) {
580 return (lo + rel, lo + rel + value.len());
581 }
582 }
583 (lo, hi)
584}
585
586fn out_name(key: &str) -> String {
588 match key {
589 "eventTrees" => "event_trees",
590 "faultTrees" => "fault_trees",
591 "basicEvents" => "basic_events",
592 "initiatingEvent" => "initiating_event",
593 "topEvent" => "top_event",
594 "onFailure" => "on_failure",
595 "onFailureProbabilitySource" => "on_failure_probability_source",
596 "probabilityOfSuccess" => "probability_of_success",
597 "probabilityOfFailure" => "probability_of_failure",
598 "probabilitySource" => "probability_source",
599 "rootCause" => "root_cause",
600 "inhibitCondition" => "inhibit_condition",
601 "retryPolicy" => "retry_policy",
602 "timeoutMs" => "timeout_ms",
603 "maxAttempts" => "max_attempts",
604 "backoffMs" => "backoff_ms",
605 "backoffStrategy" => "backoff_strategy",
606 "failureRate" => "failure_rate",
607 "missionTime" => "mission_time",
608 "eventType" => "event_type",
609 other => other,
610 }
611 .to_string()
612}
613
614impl<'a> Builder<'a> {
617 fn walk_root(&mut self, root: &MarkedYaml) {
618 let Some(map) = root.data.as_mapping() else {
619 return;
620 };
621 for (k, v) in map {
622 let key = node_str(k);
623 let out = out_name(&key);
624 let (ks, _ke) = byte_range(k);
625 let (_vs, ve) = byte_range(v);
626 let path = vec![PathPart::Key(out.clone())];
627 self.add(IndexedElement {
628 kind: ElementKind::Section,
629 name: out.clone(),
630 field: None,
631 tree: None,
632 span: self.span(ks, ve),
633 key_span: Some(self.span(ks, ks + key.len())),
634 path: path.clone(),
635 depth: 1,
636 });
637 match key.as_str() {
638 "info" => self.walk_info(v, &out),
639 "asyncapi_imports" => self.walk_imports(v, &out),
640 "components" => self.walk_components(v, &out),
641 "eventTrees" => self.walk_event_trees(v, &out),
642 "faultTrees" => self.walk_fault_trees(v, &out),
643 _ => {}
644 }
645 }
646 }
647
648 fn walk_info(&mut self, node: &MarkedYaml, base: &str) {
649 let Some(map) = node.data.as_mapping() else {
650 return;
651 };
652 for (k, v) in map {
653 let key = node_str(k);
654 let out = out_name(&key);
655 let (ks, _ke) = byte_range(k);
656 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(out.clone())];
657 self.add(IndexedElement {
658 kind: ElementKind::Field,
659 name: node_str(v),
660 field: Some(out.clone()),
661 tree: None,
662 span: self.span(ks, byte_range(v).1),
663 key_span: Some(self.span(ks, ks + key.len())),
664 path,
665 depth: 2,
666 });
667 }
668 }
669
670 fn walk_imports(&mut self, node: &MarkedYaml, base: &str) {
671 let Some(map) = node.data.as_mapping() else {
672 return;
673 };
674 for (k, v) in map {
675 let alias = node_str(k);
676 let (ks, _ke) = byte_range(k);
677 let path = vec![
678 PathPart::Key(base.to_string()),
679 PathPart::Key(alias.clone()),
680 ];
681 self.add(IndexedElement {
682 kind: ElementKind::Field,
683 name: alias.clone(),
684 field: Some(alias.clone()),
685 tree: None,
686 span: self.span(ks, byte_range(v).1),
687 key_span: Some(self.span(ks, ks + alias.len())),
688 path,
689 depth: 2,
690 });
691 }
692 }
693
694 fn walk_event_trees(&mut self, node: &MarkedYaml, base: &str) {
695 let Some(map) = node.data.as_mapping() else {
696 return;
697 };
698 for (tk, tv) in map {
699 let tree = node_str(tk);
700 let (ks, _ke) = byte_range(tk);
701 let (_, ve) = byte_range(tv);
702 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(tree.clone())];
703 self.add(IndexedElement {
704 kind: ElementKind::Definition,
705 name: tree.clone(),
706 field: None,
707 tree: Some(tree.clone()),
708 span: self.span(ks, ve),
709 key_span: Some(self.span(ks, ks + tree.len())),
710 path: path.clone(),
711 depth: 2,
712 });
713 self.walk_event_tree_fields(tv, base, &tree, &path);
714 }
715 }
716
717 fn walk_event_tree_fields(
718 &mut self,
719 node: &MarkedYaml,
720 base: &str,
721 tree: &str,
722 tree_path: &PathKey,
723 ) {
724 let Some(map) = node.data.as_mapping() else {
725 return;
726 };
727 for (k, v) in map {
728 let key = node_str(k);
729 let (ks, _ke) = byte_range(k);
730 let (_, ve) = byte_range(v);
731 let mut path = tree_path.clone();
732 match key.as_str() {
733 "initiatingEvent" => {
734 path.push(PathPart::Key("initiating_event".to_string()));
735 self.add(IndexedElement {
736 kind: ElementKind::Field,
737 name: "initiatingEvent".to_string(),
738 field: Some("initiating_event".to_string()),
739 tree: Some(tree.to_string()),
740 span: self.span(ks, ve),
741 key_span: Some(self.span(ks, ks + key.len())),
742 path: path.clone(),
743 depth: 3,
744 });
745 self.walk_initiating_event(v, tree, &path);
746 }
747 "nodes" => {
748 path.push(PathPart::Key("nodes".to_string()));
749 self.add(IndexedElement {
750 kind: ElementKind::Field,
751 name: "nodes".to_string(),
752 field: Some("nodes".to_string()),
753 tree: Some(tree.to_string()),
754 span: self.span(ks, ve),
755 key_span: Some(self.span(ks, ks + key.len())),
756 path: path.clone(),
757 depth: 3,
758 });
759 self.walk_nodes(v, base, tree, &path);
760 }
761 "description" => {
762 path.push(PathPart::Key("description".to_string()));
763 self.add(IndexedElement {
764 kind: ElementKind::Field,
765 name: node_str(v),
766 field: Some("description".to_string()),
767 tree: Some(tree.to_string()),
768 span: self.span(ks, ve),
769 key_span: Some(self.span(ks, ks + key.len())),
770 path,
771 depth: 3,
772 });
773 }
774 _ => {}
775 }
776 }
777 }
778
779 fn walk_initiating_event(&mut self, node: &MarkedYaml, tree: &str, base_path: &PathKey) {
780 self.walk_scalar_map(
781 node,
782 tree,
783 base_path,
784 &[("id", None), ("message", Some(true)), ("next", Some(true))],
785 );
786 }
787
788 fn walk_nodes(&mut self, node: &MarkedYaml, base: &str, tree: &str, nodes_path: &PathKey) {
789 let Some(map) = node.data.as_mapping() else {
790 return;
791 };
792 for (nk, nv) in map {
793 let nid = node_str(nk);
794 let (ks, _ke) = byte_range(nk);
795 let (_, ve) = byte_range(nv);
796 let mut path = nodes_path.clone();
797 path.push(PathPart::Key(nid.clone()));
798 self.add(IndexedElement {
799 kind: ElementKind::Definition,
800 name: nid.clone(),
801 field: None,
802 tree: Some(tree.to_string()),
803 span: self.span(ks, ve),
804 key_span: Some(self.span(ks, ks + nid.len())),
805 path: path.clone(),
806 depth: 4,
807 });
808 let Some(fields) = nv.data.as_mapping() else {
809 continue;
810 };
811 for (k, v) in fields {
812 let key = node_str(k);
813 let (fks, _fke) = byte_range(k);
814 let mut fpath = path.clone();
815 let field = out_name(&key);
816 fpath.push(PathPart::Key(field.clone()));
817 let is_ref = matches!(
818 key.as_str(),
819 "next"
820 | "onFailure"
821 | "onFailureProbabilitySource"
822 | "emits"
823 | "channel"
824 | "message"
825 );
826 if is_ref {
827 self.add(IndexedElement {
828 kind: ElementKind::Reference,
829 name: node_str(v),
830 field: Some(field.clone()),
831 tree: Some(tree.to_string()),
832 span: self.value_span(v),
833 key_span: None,
834 path: fpath,
835 depth: 5,
836 });
837 } else if key == "branches" {
838 self.add(IndexedElement {
839 kind: ElementKind::Field,
840 name: "branches".to_string(),
841 field: Some("branches".to_string()),
842 tree: Some(tree.to_string()),
843 span: self.span(fks, byte_range(v).1),
844 key_span: Some(self.span(fks, fks + key.len())),
845 path: fpath.clone(),
846 depth: 5,
847 });
848 self.walk_branches(v, tree, &fpath);
849 } else {
850 self.add(IndexedElement {
851 kind: ElementKind::Field,
852 name: node_str(v),
853 field: Some(field.clone()),
854 tree: Some(tree.to_string()),
855 span: self.span(fks, byte_range(v).1),
856 key_span: Some(self.span(fks, fks + key.len())),
857 path: fpath,
858 depth: 5,
859 });
860 }
861 }
862 let _ = base;
863 }
864 }
865
866 fn walk_branches(&mut self, node: &MarkedYaml, tree: &str, branches_path: &PathKey) {
867 let Some(seq) = node.data.as_vec() else {
868 return;
869 };
870 for (i, bv) in seq.iter().enumerate() {
871 let (bs, be) = byte_range(bv);
872 let mut bpath = branches_path.clone();
873 bpath.push(PathPart::Index(i));
874 self.add(IndexedElement {
875 kind: ElementKind::Field,
876 name: format!("branches[{}]", i),
877 field: Some("branches".to_string()),
878 tree: Some(tree.to_string()),
879 span: self.span(bs, be),
880 key_span: None,
881 path: bpath.clone(),
882 depth: 6,
883 });
884 let Some(map) = bv.data.as_mapping() else {
885 continue;
886 };
887 for (k, v) in map {
888 let key = node_str(k);
889 let (ks, _ke) = byte_range(k);
890 let mut fpath = bpath.clone();
891 let field = out_name(&key);
892 fpath.push(PathPart::Key(field.clone()));
893 let is_ref = matches!(key.as_str(), "next" | "probabilitySource");
894 let kind = if is_ref {
895 ElementKind::Reference
896 } else {
897 ElementKind::Field
898 };
899 self.add(IndexedElement {
900 kind,
901 name: node_str(v),
902 field: Some(field.clone()),
903 tree: Some(tree.to_string()),
904 span: if is_ref {
905 self.value_span(v)
906 } else {
907 self.span(ks, byte_range(v).1)
908 },
909 key_span: None,
910 path: fpath,
911 depth: 7,
912 });
913 }
914 }
915 }
916
917 fn walk_scalar_map(
920 &mut self,
921 node: &MarkedYaml,
922 tree: &str,
923 base_path: &PathKey,
924 spec: &[(&str, Option<bool>)],
925 ) {
926 let Some(map) = node.data.as_mapping() else {
927 return;
928 };
929 for (k, v) in map {
930 let key = node_str(k);
931 let field = out_name(&key);
932 if let Some((_, is_ref)) = spec.iter().find(|(f, _)| *f == key.as_str()) {
933 let (ks, _ke) = byte_range(k);
934 let mut path = base_path.clone();
935 path.push(PathPart::Key(field.clone()));
936 let kind = if is_ref.unwrap_or(false) {
937 ElementKind::Reference
938 } else {
939 ElementKind::Definition
940 };
941 self.add(IndexedElement {
942 kind,
943 name: node_str(v),
944 field: Some(field.clone()),
945 tree: Some(tree.to_string()),
946 span: if kind == ElementKind::Reference {
947 self.value_span(v)
948 } else {
949 self.span(ks, byte_range(v).1)
950 },
951 key_span: None,
952 path,
953 depth: 4,
954 });
955 }
956 }
957 }
958
959 fn walk_fault_trees(&mut self, node: &MarkedYaml, base: &str) {
960 let Some(map) = node.data.as_mapping() else {
961 return;
962 };
963 for (tk, tv) in map {
964 let tree = node_str(tk);
965 let (ks, _ke) = byte_range(tk);
966 let (_, ve) = byte_range(tv);
967 let path = vec![PathPart::Key(base.to_string()), PathPart::Key(tree.clone())];
968 self.add(IndexedElement {
969 kind: ElementKind::Definition,
970 name: tree.clone(),
971 field: None,
972 tree: Some(tree.clone()),
973 span: self.span(ks, ve),
974 key_span: Some(self.span(ks, ks + tree.len())),
975 path: path.clone(),
976 depth: 2,
977 });
978 self.walk_fault_tree_fields(tv, base, &tree, &path);
979 }
980 }
981
982 fn walk_fault_tree_fields(
983 &mut self,
984 node: &MarkedYaml,
985 base: &str,
986 tree: &str,
987 tree_path: &PathKey,
988 ) {
989 let Some(map) = node.data.as_mapping() else {
990 return;
991 };
992 for (k, v) in map {
993 let key = node_str(k);
994 let (ks, _ke) = byte_range(k);
995 let (_, ve) = byte_range(v);
996 let mut path = tree_path.clone();
997 match key.as_str() {
998 "topEvent" => {
999 path.push(PathPart::Key("top_event".to_string()));
1000 self.add(IndexedElement {
1001 kind: ElementKind::Field,
1002 name: "topEvent".to_string(),
1003 field: Some("top_event".to_string()),
1004 tree: Some(tree.to_string()),
1005 span: self.span(ks, ve),
1006 key_span: Some(self.span(ks, ks + key.len())),
1007 path: path.clone(),
1008 depth: 3,
1009 });
1010 self.walk_top_event(v, tree, &path);
1011 }
1012 "gates" => {
1013 path.push(PathPart::Key("gates".to_string()));
1014 self.add(IndexedElement {
1015 kind: ElementKind::Field,
1016 name: "gates".to_string(),
1017 field: Some("gates".to_string()),
1018 tree: Some(tree.to_string()),
1019 span: self.span(ks, ve),
1020 key_span: Some(self.span(ks, ks + key.len())),
1021 path: path.clone(),
1022 depth: 3,
1023 });
1024 self.walk_gates(v, base, tree, &path);
1025 }
1026 "basicEvents" => {
1027 let field = "basic_events";
1028 path.push(PathPart::Key(field.to_string()));
1029 self.add(IndexedElement {
1030 kind: ElementKind::Field,
1031 name: "basicEvents".to_string(),
1032 field: Some(field.to_string()),
1033 tree: Some(tree.to_string()),
1034 span: self.span(ks, ve),
1035 key_span: Some(self.span(ks, ks + key.len())),
1036 path: path.clone(),
1037 depth: 3,
1038 });
1039 self.walk_basic_events(v, base, tree, &path);
1040 }
1041 "transfers" => {
1042 path.push(PathPart::Key("transfers".to_string()));
1043 self.add(IndexedElement {
1044 kind: ElementKind::Field,
1045 name: "transfers".to_string(),
1046 field: Some("transfers".to_string()),
1047 tree: Some(tree.to_string()),
1048 span: self.span(ks, ve),
1049 key_span: Some(self.span(ks, ks + key.len())),
1050 path: path.clone(),
1051 depth: 3,
1052 });
1053 self.walk_transfers(v, base, tree, &path);
1054 }
1055 "description" => {
1056 path.push(PathPart::Key("description".to_string()));
1057 self.add(IndexedElement {
1058 kind: ElementKind::Field,
1059 name: node_str(v),
1060 field: Some("description".to_string()),
1061 tree: Some(tree.to_string()),
1062 span: self.span(ks, ve),
1063 key_span: Some(self.span(ks, ks + key.len())),
1064 path,
1065 depth: 3,
1066 });
1067 }
1068 _ => {}
1069 }
1070 }
1071 }
1072
1073 fn walk_top_event(&mut self, node: &MarkedYaml, tree: &str, base_path: &PathKey) {
1074 let Some(map) = node.data.as_mapping() else {
1075 return;
1076 };
1077 for (k, v) in map {
1078 let key = node_str(k);
1079 let (ks, _ke) = byte_range(k);
1080 let field = out_name(&key);
1081 let mut path = base_path.clone();
1082 path.push(PathPart::Key(field.clone()));
1083 let is_ref = matches!(key.as_str(), "message" | "rootCause");
1084 let kind = if is_ref {
1085 ElementKind::Reference
1086 } else {
1087 ElementKind::Field
1088 };
1089 self.add(IndexedElement {
1090 kind,
1091 name: node_str(v),
1092 field: Some(field.clone()),
1093 tree: Some(tree.to_string()),
1094 span: if is_ref {
1095 self.value_span(v)
1096 } else {
1097 self.span(ks, byte_range(v).1)
1098 },
1099 key_span: None,
1100 path,
1101 depth: 4,
1102 });
1103 }
1104 }
1105
1106 fn walk_gates(&mut self, node: &MarkedYaml, base: &str, tree: &str, gates_path: &PathKey) {
1107 let Some(map) = node.data.as_mapping() else {
1108 return;
1109 };
1110 for (gk, gv) in map {
1111 let gid = node_str(gk);
1112 let (ks, _ke) = byte_range(gk);
1113 let (_, ve) = byte_range(gv);
1114 let mut path = gates_path.clone();
1115 path.push(PathPart::Key(gid.clone()));
1116 self.add(IndexedElement {
1117 kind: ElementKind::Definition,
1118 name: gid.clone(),
1119 field: None,
1120 tree: Some(tree.to_string()),
1121 span: self.span(ks, ve),
1122 key_span: Some(self.span(ks, ks + gid.len())),
1123 path: path.clone(),
1124 depth: 4,
1125 });
1126 let Some(fields) = gv.data.as_mapping() else {
1127 continue;
1128 };
1129 for (k, v) in fields {
1130 let key = node_str(k);
1131 let (fks, _fke) = byte_range(k);
1132 let field = out_name(&key);
1133 let mut fpath = path.clone();
1134 fpath.push(PathPart::Key(field.clone()));
1135 match key.as_str() {
1136 "inputs" => {
1137 self.add(IndexedElement {
1138 kind: ElementKind::Field,
1139 name: node_str(v),
1140 field: Some("inputs".to_string()),
1141 tree: Some(tree.to_string()),
1142 span: self.span(fks, byte_range(v).1),
1143 key_span: Some(self.span(fks, fks + key.len())),
1144 path: fpath.clone(),
1145 depth: 5,
1146 });
1147 if let Some(seq) = v.data.as_vec() {
1148 for (i, item) in seq.iter().enumerate() {
1149 let mut ipath = fpath.clone();
1150 ipath.push(PathPart::Index(i));
1151 self.add(IndexedElement {
1152 kind: ElementKind::Reference,
1153 name: node_str(item),
1154 field: Some("inputs".to_string()),
1155 tree: Some(tree.to_string()),
1156 span: self.value_span(item),
1157 key_span: None,
1158 path: ipath,
1159 depth: 6,
1160 });
1161 }
1162 }
1163 }
1164 _ => {
1165 self.add(IndexedElement {
1166 kind: ElementKind::Field,
1167 name: node_str(v),
1168 field: Some(field.clone()),
1169 tree: Some(tree.to_string()),
1170 span: self.span(fks, byte_range(v).1),
1171 key_span: Some(self.span(fks, fks + key.len())),
1172 path: fpath,
1173 depth: 5,
1174 });
1175 }
1176 }
1177 }
1178 let _ = base;
1179 }
1180 }
1181
1182 fn walk_basic_events(
1183 &mut self,
1184 node: &MarkedYaml,
1185 base: &str,
1186 tree: &str,
1187 events_path: &PathKey,
1188 ) {
1189 let Some(map) = node.data.as_mapping() else {
1190 return;
1191 };
1192 for (ek, ev) in map {
1193 let eid = node_str(ek);
1194 let (ks, _ke) = byte_range(ek);
1195 let (_, ve) = byte_range(ev);
1196 let mut path = events_path.clone();
1197 path.push(PathPart::Key(eid.clone()));
1198 self.add(IndexedElement {
1199 kind: ElementKind::Definition,
1200 name: eid.clone(),
1201 field: None,
1202 tree: Some(tree.to_string()),
1203 span: self.span(ks, ve),
1204 key_span: Some(self.span(ks, ks + eid.len())),
1205 path: path.clone(),
1206 depth: 4,
1207 });
1208 let Some(fields) = ev.data.as_mapping() else {
1209 continue;
1210 };
1211 for (k, v) in fields {
1212 let key = node_str(k);
1213 let (fks, _fke) = byte_range(k);
1214 let field = out_name(&key);
1215 let mut fpath = path.clone();
1216 fpath.push(PathPart::Key(field.clone()));
1217 let is_ref = key == "message";
1218 let kind = if is_ref {
1219 ElementKind::Reference
1220 } else {
1221 ElementKind::Field
1222 };
1223 self.add(IndexedElement {
1224 kind,
1225 name: node_str(v),
1226 field: Some(field.clone()),
1227 tree: Some(tree.to_string()),
1228 span: if is_ref {
1229 self.value_span(v)
1230 } else {
1231 self.span(fks, byte_range(v).1)
1232 },
1233 key_span: None,
1234 path: fpath,
1235 depth: 5,
1236 });
1237 }
1238 let _ = base;
1239 }
1240 }
1241
1242 fn walk_transfers(
1243 &mut self,
1244 node: &MarkedYaml,
1245 base: &str,
1246 tree: &str,
1247 transfers_path: &PathKey,
1248 ) {
1249 let Some(map) = node.data.as_mapping() else {
1250 return;
1251 };
1252 for (tk, tv) in map {
1253 let tid = node_str(tk);
1254 let (ks, _ke) = byte_range(tk);
1255 let (_, ve) = byte_range(tv);
1256 let mut path = transfers_path.clone();
1257 path.push(PathPart::Key(tid.clone()));
1258 self.add(IndexedElement {
1259 kind: ElementKind::Definition,
1260 name: tid.clone(),
1261 field: None,
1262 tree: Some(tree.to_string()),
1263 span: self.span(ks, ve),
1264 key_span: Some(self.span(ks, ks + tid.len())),
1265 path: path.clone(),
1266 depth: 4,
1267 });
1268 let Some(fields) = tv.data.as_mapping() else {
1269 continue;
1270 };
1271 for (k, v) in fields {
1272 let key = node_str(k);
1273 let (fks, _fke) = byte_range(k);
1274 let field = out_name(&key);
1275 let mut fpath = path.clone();
1276 fpath.push(PathPart::Key(field.clone()));
1277 self.add(IndexedElement {
1278 kind: ElementKind::Field,
1279 name: node_str(v),
1280 field: Some(field.clone()),
1281 tree: Some(tree.to_string()),
1282 span: self.span(fks, byte_range(v).1),
1283 key_span: None,
1284 path: fpath,
1285 depth: 5,
1286 });
1287 }
1288 let _ = base;
1289 }
1290 }
1291
1292 fn walk_components(&mut self, node: &MarkedYaml, base: &str) {
1293 let Some(map) = node.data.as_mapping() else {
1294 return;
1295 };
1296 for (k, v) in map {
1297 let key = node_str(k);
1298 let (ks, _ke) = byte_range(k);
1299 let field = out_name(&key);
1300 let path = vec![
1301 PathPart::Key(base.to_string()),
1302 PathPart::Key(field.clone()),
1303 ];
1304 self.add(IndexedElement {
1305 kind: ElementKind::Field,
1306 name: key.clone(),
1307 field: Some(field.clone()),
1308 tree: None,
1309 span: self.span(ks, byte_range(v).1),
1310 key_span: Some(self.span(ks, ks + key.len())),
1311 path: path.clone(),
1312 depth: 2,
1313 });
1314 let Some(items) = v.data.as_mapping() else {
1315 continue;
1316 };
1317 for (ik, iv) in items {
1318 let iid = node_str(ik);
1319 let (iks, _ike) = byte_range(ik);
1320 let mut ipath = path.clone();
1321 ipath.push(PathPart::Key(iid.clone()));
1322 self.add(IndexedElement {
1323 kind: ElementKind::Definition,
1324 name: iid.clone(),
1325 field: None,
1326 tree: Some(iid.clone()),
1327 span: self.span(iks, byte_range(iv).1),
1328 key_span: Some(self.span(iks, iks + iid.len())),
1329 path: ipath.clone(),
1330 depth: 3,
1331 });
1332 let Some(fields) = iv.data.as_mapping() else {
1333 continue;
1334 };
1335 for (fk, fv) in fields {
1336 let fkey = node_str(fk);
1337 let (fks, _fke) = byte_range(fk);
1338 let fname = out_name(&fkey);
1339 let mut fpath = ipath.clone();
1340 fpath.push(PathPart::Key(fname.clone()));
1341 let is_ref = matches!(
1342 fkey.as_str(),
1343 "next" | "onFailure" | "message" | "channel" | "emits" | "inputs"
1344 );
1345 let kind = if is_ref {
1346 ElementKind::Reference
1347 } else {
1348 ElementKind::Field
1349 };
1350 self.add(IndexedElement {
1351 kind,
1352 name: node_str(fv),
1353 field: Some(fname.clone()),
1354 tree: Some(iid.clone()),
1355 span: if is_ref {
1356 self.value_span(fv)
1357 } else {
1358 self.span(fks, byte_range(fv).1)
1359 },
1360 key_span: None,
1361 path: fpath,
1362 depth: 4,
1363 });
1364 }
1365 }
1366 }
1367 }
1368}
1369
1370struct LineMap<'a> {
1375 content: &'a str,
1376 line_starts: Vec<usize>,
1377}
1378
1379impl<'a> LineMap<'a> {
1380 fn new(content: &'a str) -> Self {
1381 let mut line_starts = vec![0];
1382 for (i, b) in content.bytes().enumerate() {
1383 if b == b'\n' {
1384 line_starts.push(i + 1);
1385 }
1386 }
1387 LineMap {
1388 content,
1389 line_starts,
1390 }
1391 }
1392
1393 fn line_of(&self, byte: usize) -> usize {
1394 match self.line_starts.binary_search(&byte) {
1395 Ok(i) => i,
1396 Err(i) => i.saturating_sub(1),
1397 }
1398 .min(self.line_starts.len().saturating_sub(1))
1399 }
1400
1401 fn char_offset(&self, byte: usize) -> usize {
1402 let byte = byte.min(self.content.len());
1403 let byte = self.nearest_char_boundary(byte);
1406 self.content[..byte].chars().count()
1407 }
1408
1409 fn nearest_char_boundary(&self, byte: usize) -> usize {
1411 let byte = byte.min(self.content.len());
1412 let bytes = self.content.as_bytes();
1413 let mut b = byte;
1414 while b > 0 && b < bytes.len() && (bytes[b] & 0xC0) == 0x80 {
1417 b -= 1;
1418 }
1419 b
1420 }
1421
1422 fn span_of(&self, start: usize, end: usize) -> Span {
1423 let start = self.nearest_char_boundary(start);
1424 let end = self.nearest_char_boundary(end);
1425 let line = self.line_of(start);
1426 let line_start = self.nearest_char_boundary(self.line_starts[line]);
1427 let line_start = line_start.min(start);
1428 let column = self.content[line_start..start].chars().count();
1429 let end_line = self.line_of(end);
1430 let end_line_start = self.nearest_char_boundary(self.line_starts[end_line]);
1431 let end_line_start = end_line_start.min(end);
1432 let end_column = self.content[end_line_start..end].chars().count();
1433 Span {
1434 start: self.char_offset(start) as u32,
1435 end: self.char_offset(end) as u32,
1436 line: line as u32,
1437 column: column as u32,
1438 end_line: end_line as u32,
1439 end_column: end_column as u32,
1440 }
1441 }
1442}
1443
1444#[cfg(test)]
1445mod tests {
1446 use super::*;
1447 use serde_json::json;
1448
1449 const FIXTURE: &str = include_str!("../tests/fixtures/order-fulfillment.etdl");
1450
1451 #[test]
1452 fn index_covers_sections_and_definitions() {
1453 let index = build_span_index(FIXTURE).unwrap();
1454 let event_trees = index
1455 .resolve(&SpanKey::Section("event_trees"))
1456 .expect("event_trees section");
1457 assert_eq!(event_trees.kind, ElementKind::Section);
1458 assert_eq!(event_trees.span.line, 11); let tree = index
1461 .resolve(&SpanKey::Tree {
1462 tree: "OrderFulfillment".to_string(),
1463 })
1464 .expect("tree definition");
1465 assert_eq!(tree.kind, ElementKind::Definition);
1466 assert_eq!(tree.span.line, 12);
1467
1468 let node = index
1469 .resolve(&SpanKey::Node {
1470 tree: "OrderFulfillment".to_string(),
1471 id: "InventoryCheckBarrier".to_string(),
1472 })
1473 .expect("node definition");
1474 assert_eq!(node.span.line, 20);
1475 let key_span = node.key_span.expect("key span");
1476 assert_eq!(key_span.line, 20);
1477 assert_eq!(key_span.column, 6); }
1479
1480 #[test]
1481 fn index_covers_references() {
1482 let index = build_span_index(FIXTURE).unwrap();
1483 let next = index
1484 .resolve(&SpanKey::NodeField {
1485 tree: "OrderFulfillment".to_string(),
1486 id: "ProcessPaymentOperation".to_string(),
1487 field: "next",
1488 })
1489 .expect("next reference");
1490 assert_eq!(next.kind, ElementKind::Reference);
1491 assert_eq!(next.name, "FulfillmentConsequence");
1492 assert_eq!(next.span.line, 39);
1493
1494 let message = index
1495 .resolve(&SpanKey::InitiatingEvent {
1496 tree: "OrderFulfillment".to_string(),
1497 field: "message",
1498 })
1499 .expect("initiatingEvent.message reference");
1500 assert_eq!(message.kind, ElementKind::Reference);
1501 assert_eq!(message.name, "orders_api#/components/messages/OrderPlaced");
1502
1503 let gate_input = index
1504 .resolve(&SpanKey::GateInput {
1505 tree: "PaymentGatewayFailure".to_string(),
1506 id: "GatewayUnavailableOrRejected".to_string(),
1507 idx: 1,
1508 })
1509 .expect("gate input reference");
1510 assert_eq!(gate_input.name, "ChargeRejected");
1511
1512 let root_cause = index
1513 .resolve(&SpanKey::TopEvent {
1514 tree: "PaymentGatewayFailure".to_string(),
1515 field: "root_cause",
1516 })
1517 .expect("rootCause reference");
1518 assert_eq!(root_cause.name, "GatewayUnavailableOrRejected");
1519 }
1520
1521 #[test]
1522 fn find_deepest_resolves_reference_tokens() {
1523 let index = build_span_index(FIXTURE).unwrap();
1524 let line = FIXTURE.lines().nth(39).unwrap();
1526 let byte_offset = FIXTURE.lines().take(39).map(|l| l.len() + 1).sum::<usize>()
1527 + line.find("FulfillmentConsequence").unwrap();
1528 let char_offset = FIXTURE[..byte_offset].chars().count() as u32;
1529
1530 let el = index.find_deepest(char_offset).expect("found");
1531 assert_eq!(el.kind, ElementKind::Reference);
1532 assert_eq!(el.name, "FulfillmentConsequence");
1533 assert_eq!(el.field.as_deref(), Some("next"));
1534 assert_eq!(el.tree.as_deref(), Some("OrderFulfillment"));
1535 assert!(el.span.start <= char_offset && char_offset < el.span.end);
1536 }
1537
1538 #[test]
1539 fn inject_spans_wraps_scalars() {
1540 let (doc, index) = parse_document_with_spans(FIXTURE).unwrap();
1541 let mut value = serde_json::to_value(&doc).unwrap();
1542 inject_spans(&mut value, &index);
1543
1544 let event_trees = value.get("event_trees").expect("event_trees");
1545 assert!(event_trees.get("span").is_some(), "section span attached");
1546 let tree = &event_trees["OrderFulfillment"];
1547 assert!(tree.get("span").is_some());
1548 let node = &tree["nodes"]["InventoryCheckBarrier"];
1549 assert!(node.get("span").is_some(), "node block span attached");
1550
1551 let op = &tree["nodes"]["ProcessPaymentOperation"];
1553 let next = &op["next"];
1554 assert!(next.is_object());
1555 assert_eq!(next["value"], "FulfillmentConsequence");
1556 assert!(next.get("span").is_some());
1557
1558 assert_eq!(op["retry_policy"]["max_attempts"], 3);
1560 assert_eq!(op["action"]["value"], "execute");
1561 assert!(op["action"].get("span").is_some());
1562 }
1563
1564 #[test]
1565 fn duplicate_ids_are_detected() {
1566 let yaml = r#"
1567etdl: "1.0.0"
1568info:
1569 title: "T"
1570 version: "1.0.0"
1571 domain: "D"
1572asyncapi_imports: {}
1573eventTrees:
1574 T:
1575 initiatingEvent:
1576 id: I
1577 message: "a#/m"
1578 next: N
1579 nodes:
1580 N:
1581 type: barrier
1582 branches:
1583 - outcome: ok
1584 condition: "default"
1585 next: M
1586 M:
1587 type: consequence
1588 operation: terminate
1589 N:
1590 type: consequence
1591 operation: terminate
1592"#;
1593 let dups = detect_duplicate_ids(yaml).unwrap();
1594 assert_eq!(dups.len(), 1, "expected one duplicate node id");
1595 assert_eq!(dups[0].kind, "node");
1596 assert_eq!(dups[0].id, "N");
1597 assert_eq!(dups[0].tree, "T");
1598 }
1599
1600 #[test]
1601 fn span_key_resolves_to_path() {
1602 let index = build_span_index(FIXTURE).unwrap();
1603 let branch = index
1604 .resolve(&SpanKey::BranchField {
1605 tree: "OrderFulfillment".to_string(),
1606 id: "InventoryCheckBarrier".to_string(),
1607 branch: 1,
1608 field: "next",
1609 })
1610 .expect("branch next reference");
1611 assert_eq!(branch.name, "OutOfStockConsequence");
1612 let _ = json!({});
1613 }
1614}