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