1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use super::{LinkOccurrenceRef, ReferenceOwnerRef};
7use crate::{
8 ContentLocation, ContentLocationRef, DocumentReference, EntryOwnerLocationRef, FragmentAlias,
9 LinkTarget, NodeId, SourceSpan,
10};
11
12#[derive(
14 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
15)]
16#[serde(rename_all = "kebab-case")]
17pub enum ReferenceTargetType {
18 Document,
20 Manual,
22 Local,
24 External,
26 Email,
28}
29
30impl ReferenceTargetType {
31 #[must_use]
33 pub const fn of(target: &LinkTarget) -> Self {
34 match target {
35 LinkTarget::Document { .. } => Self::Document,
36 LinkTarget::Manual { .. } => Self::Manual,
37 LinkTarget::Section { .. } => Self::Local,
38 LinkTarget::External { .. } => Self::External,
39 LinkTarget::Email { .. } => Self::Email,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct ReferenceLinkFilter(u8);
47
48impl ReferenceLinkFilter {
49 pub const NONE: Self = Self(0);
51 pub const ALL: Self = Self(31);
53 pub const DOCUMENTS: Self = Self(3);
55
56 #[must_use]
58 pub fn from_types(types: &[ReferenceTargetType]) -> Self {
59 Self(types.iter().fold(0, |bits, kind| bits | (1 << *kind as u8)))
60 }
61
62 #[must_use]
64 pub const fn contains(self, target: &LinkTarget) -> bool {
65 self.0 & (1 << ReferenceTargetType::of(target) as u8) != 0
66 }
67}
68
69#[derive(Debug, Clone, Copy)]
71pub struct NavigationScanOptions {
72 pub links: ReferenceLinkFilter,
74 pub targets: bool,
76 pub entry_sets: bool,
78}
79
80impl Default for NavigationScanOptions {
81 fn default() -> Self {
82 Self {
83 links: ReferenceLinkFilter::ALL,
84 targets: false,
85 entry_sets: false,
86 }
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
92#[serde(
93 tag = "kind",
94 rename_all = "kebab-case",
95 rename_all_fields = "camelCase",
96 deny_unknown_fields
97)]
98pub enum ContentReveal {
99 Document {},
101 Section {
103 sections: Vec<u32>,
105 },
106 Owner {
108 sections: Vec<u32>,
110 blocks: Vec<crate::ContentBlockStep>,
112 item_index: u32,
114 },
115 Inline {
117 location: ContentLocation,
119 },
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum ContentRevealRef<'a> {
125 Document,
127 Section(&'a [u32]),
129 Owner(EntryOwnerLocationRef<'a>),
131 Inline(ContentLocationRef<'a>),
133}
134
135impl ContentReveal {
136 #[must_use]
138 pub fn as_ref(&self) -> ContentRevealRef<'_> {
139 match self {
140 Self::Document {} => ContentRevealRef::Document,
141 Self::Section { sections } => ContentRevealRef::Section(sections),
142 Self::Owner {
143 sections,
144 blocks,
145 item_index,
146 } => ContentRevealRef::Owner(EntryOwnerLocationRef {
147 sections,
148 blocks,
149 item_index: *item_index,
150 }),
151 Self::Inline { location } => ContentRevealRef::Inline(location.as_ref()),
152 }
153 }
154}
155
156impl ContentRevealRef<'_> {
157 #[must_use]
159 pub fn depth(self) -> usize {
160 match self {
161 Self::Document => 0,
162 Self::Section(sections) => sections.len(),
163 Self::Owner(owner) => owner
164 .sections
165 .len()
166 .saturating_add(owner.blocks.len())
167 .saturating_add(1),
168 Self::Inline(location) => location.depth(),
169 }
170 }
171
172 #[must_use]
174 pub fn encoded_size_bound(self) -> usize {
175 match self {
176 Self::Document => r#"{"kind":"document"}"#.len(),
177 Self::Section(sections) => {
178 ContentLocationRef::SectionHeading {
179 sections,
180 path: &[],
181 }
182 .encoded_len()
183 - "-heading".len()
184 - ",\"path\":[]".len()
185 }
186 Self::Owner(owner) => {
187 let raw = ContentLocationRef::Content {
188 sections: owner.sections,
189 blocks: owner.blocks,
190 root: crate::ContentInlineRoot::Inlines,
191 path: &[],
192 }
193 .encoded_len();
194 raw - "content".len() + "owner".len()
195 - ",\"root\":{\"kind\":\"inlines\"},\"path\":[]".len()
196 + ",\"itemIndex\":".len()
197 + owner
198 .item_index
199 .checked_ilog10()
200 .map_or(1, |n| n as usize + 1)
201 }
202 Self::Inline(location) => location
203 .encoded_len()
204 .saturating_add(r#"{"kind":"inline","location":}"#.len()),
205 }
206 }
207
208 #[must_use]
210 pub fn to_owned(self) -> Option<ContentReveal> {
211 if self.depth() > crate::MAX_CONTENT_DEPTH
212 || self.encoded_size_bound() > crate::MAX_CONTENT_LOCATION_BYTES
213 {
214 return None;
215 }
216 Some(match self {
217 Self::Document => ContentReveal::Document {},
218 Self::Section(sections) => ContentReveal::Section {
219 sections: sections.to_vec(),
220 },
221 Self::Owner(owner) => ContentReveal::Owner {
222 sections: owner.sections.to_vec(),
223 blocks: owner.blocks.to_vec(),
224 item_index: owner.item_index,
225 },
226 Self::Inline(location) => ContentReveal::Inline {
227 location: location.to_owned()?,
228 },
229 })
230 }
231}
232
233#[derive(Debug, Clone, Copy)]
235pub struct NavigationTargetRef<'ir, 'path> {
236 pub id: &'ir NodeId,
238 pub aliases: &'ir [FragmentAlias],
240 pub reveal: ContentRevealRef<'path>,
242}
243
244#[derive(Debug, Clone, Copy)]
246pub struct EntrySetReferenceRef<'ir, 'path> {
247 pub reference: &'ir DocumentReference,
249 pub owner: ReferenceOwnerRef<'ir, 'path>,
251 pub source: Option<SourceSpan>,
253}
254
255#[derive(Debug, Clone, Copy)]
257pub enum NavigationEvent<'ir, 'path> {
258 Link(LinkOccurrenceRef<'ir, 'path>),
260 Target(NavigationTargetRef<'ir, 'path>),
262 EntrySet(EntrySetReferenceRef<'ir, 'path>),
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 #[test]
270 fn reveal_sizes_match_closed_wire_and_do_not_reject_deep_sections() {
271 let sections = vec![u32::MAX; 200];
272 let blocks = [crate::ContentBlockStep::Block { index: 123 }];
273 for reveal in [
274 ContentRevealRef::Document,
275 ContentRevealRef::Section(§ions),
276 ContentRevealRef::Owner(EntryOwnerLocationRef {
277 sections: &[2],
278 blocks: &blocks,
279 item_index: 12,
280 }),
281 ContentRevealRef::Inline(ContentLocationRef::DocumentHeading { path: &[1] }),
282 ] {
283 let owned = reveal.to_owned().unwrap();
284 assert_eq!(
285 reveal.encoded_size_bound(),
286 serde_json::to_vec(&owned).unwrap().len()
287 );
288 }
289 }
290}