1use core::{fmt, str::FromStr};
4
5use crate::{Book, Language, ParseError, ReferenceParser};
6
7pub const MAX_CHAPTER_NUMBER: u16 = 999;
9
10pub const MAX_VERSE_NUMBER: u16 = 999;
12
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
15pub enum Coordinate {
16 Chapter,
18 Verse,
20}
21
22impl fmt::Display for Coordinate {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 formatter.write_str(match self {
25 Self::Chapter => "chapter",
26 Self::Verse => "verse",
27 })
28 }
29}
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct CoordinateError {
34 coordinate: Coordinate,
35 value: u16,
36 maximum: u16,
37}
38
39impl CoordinateError {
40 #[must_use]
42 pub const fn coordinate(self) -> Coordinate {
43 self.coordinate
44 }
45
46 #[must_use]
48 pub const fn value(self) -> u16 {
49 self.value
50 }
51
52 #[must_use]
54 pub const fn maximum(self) -> u16 {
55 self.maximum
56 }
57}
58
59impl fmt::Display for CoordinateError {
60 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61 write!(
62 formatter,
63 "{} must be between 1 and {} (got {})",
64 self.coordinate, self.maximum, self.value
65 )
66 }
67}
68
69impl std::error::Error for CoordinateError {}
70
71#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
73pub struct VerseRef {
74 book: Book,
75 chapter: u16,
76 verse: u16,
77}
78
79impl VerseRef {
80 pub const fn new(book: Book, chapter: u16, verse: u16) -> Result<Self, CoordinateError> {
82 if chapter == 0 || chapter > MAX_CHAPTER_NUMBER {
83 return Err(CoordinateError {
84 coordinate: Coordinate::Chapter,
85 value: chapter,
86 maximum: MAX_CHAPTER_NUMBER,
87 });
88 }
89 if verse == 0 || verse > MAX_VERSE_NUMBER {
90 return Err(CoordinateError {
91 coordinate: Coordinate::Verse,
92 value: verse,
93 maximum: MAX_VERSE_NUMBER,
94 });
95 }
96 Ok(Self {
97 book,
98 chapter,
99 verse,
100 })
101 }
102
103 pub fn parse_with_language(input: &str, language: Language) -> Result<Self, ParseError> {
105 ReferenceParser::new().parse_verse_with_language(input, language)
106 }
107
108 #[must_use]
110 pub fn try_parse(input: &str) -> Option<Self> {
111 input.parse().ok()
112 }
113
114 #[must_use]
117 pub fn try_parse_with_language(input: &str, language: Language) -> Option<Self> {
118 Self::parse_with_language(input, language).ok()
119 }
120
121 #[must_use]
123 pub const fn book(self) -> Book {
124 self.book
125 }
126
127 #[must_use]
129 pub const fn chapter(self) -> u16 {
130 self.chapter
131 }
132
133 #[must_use]
135 pub const fn verse(self) -> u16 {
136 self.verse
137 }
138
139 #[must_use]
141 pub const fn with_book(self, book: Book) -> Self {
142 Self { book, ..self }
143 }
144
145 pub const fn with_chapter(self, chapter: u16) -> Result<Self, CoordinateError> {
147 Self::new(self.book, chapter, self.verse)
148 }
149
150 pub const fn with_verse(self, verse: u16) -> Result<Self, CoordinateError> {
152 Self::new(self.book, self.chapter, verse)
153 }
154}
155
156impl fmt::Display for VerseRef {
157 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(
159 formatter,
160 "{} {}:{}",
161 self.book.full_name(),
162 self.chapter,
163 self.verse
164 )
165 }
166}
167
168impl FromStr for VerseRef {
169 type Err = ParseError;
170
171 fn from_str(input: &str) -> Result<Self, Self::Err> {
172 ReferenceParser::new().parse_verse(input)
173 }
174}
175
176#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub struct RangeOrderError {
179 start: VerseRef,
180 end: VerseRef,
181}
182
183impl RangeOrderError {
184 #[must_use]
186 pub const fn start(self) -> VerseRef {
187 self.start
188 }
189
190 #[must_use]
192 pub const fn end(self) -> VerseRef {
193 self.end
194 }
195}
196
197impl fmt::Display for RangeOrderError {
198 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(
200 formatter,
201 "range end {} must come after start {}",
202 self.end, self.start
203 )
204 }
205}
206
207impl std::error::Error for RangeOrderError {}
208
209#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
211pub struct VerseRange {
212 start: VerseRef,
213 end: VerseRef,
214}
215
216impl VerseRange {
217 pub const fn new(start: VerseRef, end: VerseRef) -> Result<Self, RangeOrderError> {
219 let ascending = (start.book as u8) < (end.book as u8)
221 || ((start.book as u8) == (end.book as u8)
222 && (start.chapter < end.chapter
223 || (start.chapter == end.chapter && start.verse < end.verse)));
224 if !ascending {
225 return Err(RangeOrderError { start, end });
226 }
227 Ok(Self { start, end })
228 }
229
230 pub const fn with_start(self, start: VerseRef) -> Result<Self, RangeOrderError> {
232 Self::new(start, self.end)
233 }
234
235 pub const fn with_end(self, end: VerseRef) -> Result<Self, RangeOrderError> {
237 Self::new(self.start, end)
238 }
239
240 pub fn parse_with_language(input: &str, language: Language) -> Result<Self, ParseError> {
242 ReferenceParser::new().parse_range_with_language(input, language)
243 }
244
245 #[must_use]
247 pub fn try_parse(input: &str) -> Option<Self> {
248 input.parse().ok()
249 }
250
251 #[must_use]
254 pub fn try_parse_with_language(input: &str, language: Language) -> Option<Self> {
255 Self::parse_with_language(input, language).ok()
256 }
257
258 #[must_use]
260 pub const fn start(self) -> VerseRef {
261 self.start
262 }
263
264 #[must_use]
266 pub const fn end(self) -> VerseRef {
267 self.end
268 }
269}
270
271impl fmt::Display for VerseRange {
272 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
273 let start = self.start;
274 let end = self.end;
275 if start.book == end.book {
276 if start.chapter == end.chapter {
277 return write!(
278 formatter,
279 "{} {}:{}-{}",
280 start.book.full_name(),
281 start.chapter,
282 start.verse,
283 end.verse
284 );
285 }
286 return write!(
287 formatter,
288 "{} {}:{}-{}:{}",
289 start.book.full_name(),
290 start.chapter,
291 start.verse,
292 end.chapter,
293 end.verse
294 );
295 }
296 write!(formatter, "{start}-{end}")
297 }
298}
299
300impl FromStr for VerseRange {
301 type Err = ParseError;
302
303 fn from_str(input: &str) -> Result<Self, Self::Err> {
304 ReferenceParser::new().parse_range(input)
305 }
306}
307
308#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
310pub enum Reference {
311 Verse(VerseRef),
313 Range(VerseRange),
315}
316
317impl Reference {
318 pub fn parse_with_language(input: &str, language: Language) -> Result<Self, ParseError> {
320 ReferenceParser::new().parse_with_language(input, language)
321 }
322
323 #[must_use]
326 pub fn try_parse(input: &str) -> Option<Self> {
327 input.parse().ok()
328 }
329
330 #[must_use]
333 pub fn try_parse_with_language(input: &str, language: Language) -> Option<Self> {
334 Self::parse_with_language(input, language).ok()
335 }
336
337 #[must_use]
339 pub const fn start(self) -> VerseRef {
340 match self {
341 Self::Verse(verse) => verse,
342 Self::Range(range) => range.start,
343 }
344 }
345
346 #[must_use]
348 pub const fn end(self) -> VerseRef {
349 match self {
350 Self::Verse(verse) => verse,
351 Self::Range(range) => range.end,
352 }
353 }
354
355 #[must_use]
357 pub const fn as_verse(self) -> Option<VerseRef> {
358 match self {
359 Self::Verse(verse) => Some(verse),
360 Self::Range(_) => None,
361 }
362 }
363
364 #[must_use]
366 pub const fn as_range(self) -> Option<VerseRange> {
367 match self {
368 Self::Verse(_) => None,
369 Self::Range(range) => Some(range),
370 }
371 }
372}
373
374impl From<VerseRef> for Reference {
375 fn from(value: VerseRef) -> Self {
376 Self::Verse(value)
377 }
378}
379
380impl From<VerseRange> for Reference {
381 fn from(value: VerseRange) -> Self {
382 Self::Range(value)
383 }
384}
385
386impl fmt::Display for Reference {
387 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
388 match self {
389 Self::Verse(verse) => verse.fmt(formatter),
390 Self::Range(range) => range.fmt(formatter),
391 }
392 }
393}
394
395impl FromStr for Reference {
396 type Err = ParseError;
397
398 fn from_str(input: &str) -> Result<Self, Self::Err> {
399 ReferenceParser::new().parse(input)
400 }
401}
402
403#[cfg(test)]
404#[path = "../tests/unit/reference.rs"]
405mod tests;