1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::fmt;
use crate::{bible::BibleError, chapter::Chapter, verse::Verse};
/// Represents a book of the Bible.
///
/// A book contains multiple chapters and has an abbreviation and title.
#[derive(Debug, Clone)]
pub struct Book {
abbrev: String, // keep the JSON key, no assumptions about canon
title: String,
chapters: Vec<Chapter>,
}
impl Book {
/// Creates a new book with the given abbreviation, title, and chapters.
///
/// # Arguments
///
/// * `abbrev` - The book's abbreviation (e.g., "gn" for Genesis)
/// * `title` - The full title of the book
/// * `chapters` - A vector of chapters in this book
pub fn new(abbrev: String, title: String, chapters: Vec<Chapter>) -> Self {
Book {
abbrev: abbrev.to_ascii_lowercase(),
title,
chapters,
}
}
/// Returns the book's abbreviation.
pub fn abbrev(&self) -> &str {
&self.abbrev
}
/// Returns the book's full title.
pub fn title(&self) -> &str {
&self.title
}
/// Returns a slice of all chapters in this book.
pub fn chapters(&self) -> &[Chapter] {
&self.chapters
}
/// Returns a specific chapter by its chapter number.
///
/// # Arguments
///
/// * `chapter_number` - The chapter number to retrieve
///
/// # Returns
///
/// The requested chapter or a descriptive error if the chapter number is invalid.
pub fn get_chapter(&self, chapter_number: usize) -> Result<&Chapter, BibleError> {
if chapter_number == 0 {
return Err(BibleError::ChapterOutOfBounds {
book_abbrev: self.abbrev.clone(),
book_name: self.title.clone(),
chapter: chapter_number,
max_chapter: self.chapters.len(),
});
}
self.chapters
.get(chapter_number - 1)
.ok_or_else(|| BibleError::ChapterOutOfBounds {
book_abbrev: self.abbrev.clone(),
book_name: self.title.clone(),
chapter: chapter_number,
max_chapter: self.chapters.len(),
})
}
/// Returns all verses from a specific chapter.
///
/// # Arguments
///
/// * `chapter_number` - The chapter number to retrieve verses from
///
/// # Returns
///
/// The verses from the requested chapter or a descriptive error.
pub fn get_verses(&self, chapter_number: usize) -> Result<&[Verse], BibleError> {
self.get_chapter(chapter_number).map(|c| c.get_verses())
}
/// Returns a specific verse by chapter and verse number.
///
/// # Arguments
///
/// * `chapter_number` - The chapter number
/// * `verse_number` - The verse number within the chapter
///
/// # Returns
///
/// The requested verse or a descriptive error if the chapter or verse is invalid.
pub fn get_verse(
&self,
chapter_number: usize,
verse_number: usize,
) -> Result<&Verse, BibleError> {
let chapter = self.get_chapter(chapter_number)?;
chapter
.get_verse(verse_number)
.ok_or_else(|| BibleError::VerseOutOfBounds {
book_abbrev: self.abbrev.clone(),
book_name: self.title.clone(),
chapter: chapter_number,
verse: verse_number,
max_verse: chapter.get_verses().len(),
})
}
}
impl fmt::Display for Book {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Book: {} ({})", self.title, self.abbrev)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{bible_books_enum::BibleBook, verse::Verse};
fn create_test_chapter() -> Chapter {
let verses = vec![Verse::new(BibleBook::Genesis, 1, 1, "Test".into())];
Chapter::new(verses, 1)
}
#[test]
fn test_book_methods() {
let book = Book::new("GN".into(), "Genesis".into(), vec![create_test_chapter()]);
assert_eq!(book.abbrev(), "gn");
assert_eq!(book.title(), "Genesis");
assert!(book.get_chapter(1).is_ok());
assert!(book.get_chapter(0).is_err());
}
#[test]
fn test_clone_independence() {
let book = Book::new("GN".into(), "Genesis".into(), vec![create_test_chapter()]);
let cloned = book.clone();
assert_eq!(book.abbrev(), cloned.abbrev());
assert_eq!(book.title(), cloned.title());
assert_eq!(book.chapters().len(), cloned.chapters().len());
// Ensure cloned book owns its data
assert_ne!(book.title().as_ptr(), cloned.title().as_ptr());
assert_ne!(book.chapters().as_ptr(), cloned.chapters().as_ptr());
}
}