switchback_traits/model/resolved.rs
1//! Parser-side index over a whole manual for link extraction.
2
3use std::collections::HashMap;
4
5use crate::ids::{EntityId, GroupId, ModuleId};
6use crate::model::entity::StoredEntity;
7use crate::model::link::EntityRef;
8use crate::model::manual::ReferenceManual;
9
10/// Flat index entry for one entity in a resolved manual.
11///
12/// In-memory only; built by parsers before link extraction. Not a wire message.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct IndexedEntity {
15 /// Module containing the entity.
16 pub module_id: ModuleId,
17 /// Contract family name for the entity's contract.
18 pub contract_family: String,
19 /// Group containing the entity.
20 pub group_id: GroupId,
21 /// Stored entity payload (may be a clone from a parser-side view).
22 pub entity: StoredEntity,
23}
24
25/// Whole-manual address space used by [`LinkExtractor`](crate::traits::LinkExtractor).
26///
27/// Population logic lives in parser crates; this crate defines the shell only.
28/// In-memory only; not serialized on the wire.
29#[derive(Clone, Debug, Default, PartialEq, Eq)]
30pub struct ResolvedManual {
31 /// Flat list of all indexed entities across modules and contracts.
32 pub entities: Vec<IndexedEntity>,
33 /// Reverse lookup from wire [`EntityRef`] to parser-side [`EntityId`].
34 pub by_ref: HashMap<EntityRef, EntityId>,
35}
36
37impl ResolvedManual {
38 /// Builds a resolved manual from a wire-safe [`ReferenceManual`].
39 ///
40 /// Walks all modules, contracts, groups, and stored entities. When duplicate
41 /// `(module, group, category, name)` keys appear, the last entity wins in
42 /// [`Self::by_ref`].
43 pub fn from_reference_manual(manual: &ReferenceManual) -> Self {
44 let mut indexed = Vec::new();
45 for module in &manual.modules {
46 for contract in &module.contracts {
47 for group in &contract.groups {
48 for entity in &group.entities {
49 indexed.push(IndexedEntity {
50 module_id: module.id.clone(),
51 contract_family: contract.family.clone(),
52 group_id: group.id.clone(),
53 entity: entity.clone(),
54 });
55 }
56 }
57 }
58 }
59 Self::new(indexed)
60 }
61
62 /// Builds a resolved manual and populates `by_ref` from the entity list.
63 pub fn new(entities: Vec<IndexedEntity>) -> Self {
64 let mut by_ref = HashMap::new();
65 for indexed in &entities {
66 let entity_ref = EntityRef {
67 module: indexed.module_id.as_str().to_string(),
68 group: indexed.group_id.as_str().to_string(),
69 category: indexed.entity.category.clone(),
70 name: indexed.entity.name.clone(),
71 };
72 let entity_id = EntityId::new(
73 indexed.group_id.clone(),
74 indexed.entity.category.clone(),
75 indexed.entity.name.clone(),
76 );
77 by_ref.insert(entity_ref, entity_id);
78 }
79 Self { entities, by_ref }
80 }
81}