Builds, runs, and tests succeed*
*Tests only succeed if you run them in a single-threaded instance. With cargo this means invoking them as `cargo run -- --test-threads=1`. There is an instance of global state in the Sword engine with the VersificationMgr class that can easily cause a race condition. Since VersificationMgr is intended to be a global singleton, when it is first being instantiated the pointer is null. Sometimes the tests get into that method at the same time and overwrite one another with the instance of the manager, thus causing some of the VerseKeys that get instantiated to be invalid. TODO: On the Rust side of the code, create an instance of the VersificationMgr before allowing any VerseKey objects to be created. Probably also necessary to run it before allowing SWMgr to be instantiated, as well, just to be safe.
This commit is contained in:
+149
-315
@@ -1,183 +1,181 @@
|
||||
use crate::cxx::ffi;
|
||||
use std::fmt;
|
||||
|
||||
/// Safe wrapper for owned VerseKey instances
|
||||
/// Safe Rust wrapper for VerseKey with proper memory management
|
||||
pub struct VerseKey {
|
||||
inner: cxx::UniquePtr<ffi::VerseKey>,
|
||||
ptr: *mut ffi::VerseKey,
|
||||
}
|
||||
|
||||
impl VerseKey {
|
||||
/// Create a new VerseKey
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: ffi::new_verse_key(),
|
||||
let ptr = ffi::new_verse_key();
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to create VerseKey");
|
||||
}
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Create a new VerseKey from text
|
||||
pub fn from_text(text: &str) -> Self {
|
||||
Self {
|
||||
inner: ffi::new_verse_key_from_text(&text.to_string()),
|
||||
let ptr = ffi::new_verse_key_from_text(text.to_string());
|
||||
if ptr.is_null() {
|
||||
panic!("Failed to create VerseKey from text");
|
||||
}
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Get the book name
|
||||
pub fn get_book_name(&self) -> String {
|
||||
self.inner.get_book_name()
|
||||
/// Create from a raw pointer (for internal use)
|
||||
pub(crate) fn from_raw_ptr(ptr: *mut ffi::VerseKey) -> Self {
|
||||
if ptr.is_null() {
|
||||
panic!("Cannot create VerseKey from null pointer");
|
||||
}
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Get the book abbreviation
|
||||
pub fn get_book_abbrev(&self) -> String {
|
||||
self.inner.get_book_abbrev()
|
||||
/// Get a reference to the underlying VerseKey
|
||||
fn as_ref(&self) -> &ffi::VerseKey {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
|
||||
/// Get the OSIS book name
|
||||
pub fn get_osis_book_name(&self) -> String {
|
||||
self.inner.get_osis_book_name()
|
||||
/// Get a pinned mutable reference to the underlying VerseKey
|
||||
fn as_pin_mut(&mut self) -> std::pin::Pin<&mut ffi::VerseKey> {
|
||||
unsafe { std::pin::Pin::new_unchecked(&mut *self.ptr) }
|
||||
}
|
||||
|
||||
/// Get the OSIS reference
|
||||
pub fn get_osis_ref(&self) -> String {
|
||||
self.inner.get_osis_ref()
|
||||
/// Check if the VerseKey is valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.ptr.is_null() && self.as_ref().is_valid()
|
||||
}
|
||||
|
||||
/// Get the OSIS reference range text
|
||||
pub fn get_osis_ref_range_text(&self) -> String {
|
||||
self.inner.get_osis_ref_range_text()
|
||||
}
|
||||
|
||||
/// Get the short range text
|
||||
pub fn get_short_range_text(&self) -> String {
|
||||
self.inner.get_short_range_text()
|
||||
}
|
||||
|
||||
/// Get the testament number
|
||||
pub fn get_testament(&self) -> u8 {
|
||||
self.inner.get_testament()
|
||||
}
|
||||
|
||||
/// Get the maximum testament number
|
||||
pub fn get_testament_max(&self) -> u8 {
|
||||
self.inner.get_testament_max()
|
||||
}
|
||||
|
||||
/// Get the book number
|
||||
pub fn get_book(&self) -> u8 {
|
||||
self.inner.get_book()
|
||||
}
|
||||
|
||||
/// Get the maximum book number
|
||||
pub fn get_book_max(&self) -> u8 {
|
||||
self.inner.get_book_max()
|
||||
}
|
||||
|
||||
/// Get the chapter number
|
||||
pub fn get_chapter(&self) -> u8 {
|
||||
self.inner.get_chapter()
|
||||
}
|
||||
|
||||
/// Get the maximum chapter number
|
||||
pub fn get_chapter_max(&self) -> u8 {
|
||||
self.inner.get_chapter_max()
|
||||
}
|
||||
|
||||
/// Get the verse number
|
||||
pub fn get_verse(&self) -> u8 {
|
||||
self.inner.get_verse()
|
||||
}
|
||||
|
||||
/// Get the maximum verse number
|
||||
pub fn get_verse_max(&self) -> u8 {
|
||||
self.inner.get_verse_max()
|
||||
}
|
||||
|
||||
/// Check if auto-normalize is enabled
|
||||
pub fn is_autonormalize(&self) -> bool {
|
||||
self.inner.is_autonormalize()
|
||||
}
|
||||
|
||||
/// Check if intros are enabled
|
||||
pub fn is_intros(&self) -> bool {
|
||||
self.inner.is_intros()
|
||||
}
|
||||
|
||||
/// Get the error code
|
||||
// Read-only methods
|
||||
pub fn get_error(&self) -> u8 {
|
||||
self.inner.get_error()
|
||||
self.as_ref().get_error()
|
||||
}
|
||||
|
||||
/// Get the locale
|
||||
pub fn get_locale(&self) -> String {
|
||||
self.inner.get_locale()
|
||||
self.as_ref().get_locale()
|
||||
}
|
||||
|
||||
/// Get the range text
|
||||
pub fn get_range_text(&self) -> String {
|
||||
self.inner.get_range_text()
|
||||
self.as_ref().get_range_text()
|
||||
}
|
||||
|
||||
/// Get the short text
|
||||
pub fn get_short_text(&self) -> String {
|
||||
self.inner.get_short_text()
|
||||
self.as_ref().get_short_text()
|
||||
}
|
||||
|
||||
/// Get the text
|
||||
pub fn get_text(&self) -> String {
|
||||
self.inner.get_text()
|
||||
self.as_ref().get_text()
|
||||
}
|
||||
|
||||
/// Normalize the key
|
||||
pub fn get_book_name(&self) -> String {
|
||||
self.as_ref().get_book_name()
|
||||
}
|
||||
|
||||
pub fn get_book_abbrev(&self) -> String {
|
||||
self.as_ref().get_book_abbrev()
|
||||
}
|
||||
|
||||
pub fn get_osis_book_name(&self) -> String {
|
||||
self.as_ref().get_osis_book_name()
|
||||
}
|
||||
|
||||
pub fn get_osis_ref(&self) -> String {
|
||||
self.as_ref().get_osis_ref()
|
||||
}
|
||||
|
||||
pub fn get_osis_ref_range_text(&self) -> String {
|
||||
self.as_ref().get_osis_ref_range_text()
|
||||
}
|
||||
|
||||
pub fn get_short_range_text(&self) -> String {
|
||||
self.as_ref().get_short_range_text()
|
||||
}
|
||||
|
||||
pub fn get_testament(&self) -> u8 {
|
||||
self.as_ref().get_testament()
|
||||
}
|
||||
|
||||
pub fn get_testament_max(&self) -> u8 {
|
||||
self.as_ref().get_testament_max()
|
||||
}
|
||||
|
||||
pub fn get_book(&self) -> u8 {
|
||||
self.as_ref().get_book()
|
||||
}
|
||||
|
||||
pub fn get_book_max(&self) -> u8 {
|
||||
self.as_ref().get_book_max()
|
||||
}
|
||||
|
||||
pub fn get_chapter(&self) -> u8 {
|
||||
self.as_ref().get_chapter()
|
||||
}
|
||||
|
||||
pub fn get_chapter_max(&self) -> u8 {
|
||||
self.as_ref().get_chapter_max()
|
||||
}
|
||||
|
||||
pub fn get_verse(&self) -> u8 {
|
||||
self.as_ref().get_verse()
|
||||
}
|
||||
|
||||
pub fn get_verse_max(&self) -> u8 {
|
||||
self.as_ref().get_verse_max()
|
||||
}
|
||||
|
||||
pub fn is_autonormalize(&self) -> bool {
|
||||
self.as_ref().is_autonormalize()
|
||||
}
|
||||
|
||||
pub fn is_intros(&self) -> bool {
|
||||
self.as_ref().is_intros()
|
||||
}
|
||||
|
||||
// Mutable methods
|
||||
pub fn normalize(&mut self, autocheck: bool) {
|
||||
self.inner.pin_mut().normalize(autocheck)
|
||||
self.as_pin_mut().normalize(autocheck)
|
||||
}
|
||||
|
||||
/// Pop an error
|
||||
pub fn pop_error(&mut self) -> u8 {
|
||||
self.inner.pin_mut().pop_error()
|
||||
self.as_pin_mut().pop_error()
|
||||
}
|
||||
|
||||
/// Set auto-normalize
|
||||
pub fn set_autonormalize(&mut self, auto_normalize: bool) {
|
||||
self.inner.pin_mut().set_autonormalize(auto_normalize)
|
||||
}
|
||||
|
||||
/// Set the book name
|
||||
pub fn set_book_name(&mut self, book_name: &str) {
|
||||
self.inner.pin_mut().set_book_name(&book_name.to_string())
|
||||
}
|
||||
|
||||
/// Set intros
|
||||
pub fn set_intros(&mut self, intros: bool) {
|
||||
self.inner.pin_mut().set_intros(intros)
|
||||
}
|
||||
|
||||
/// Set the locale
|
||||
pub fn set_locale(&mut self, locale: &str) {
|
||||
self.inner.pin_mut().set_locale(&locale.to_string())
|
||||
self.as_pin_mut().set_locale(&locale.to_string())
|
||||
}
|
||||
|
||||
/// Set the text
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
self.inner.pin_mut().set_text(&text.to_string())
|
||||
self.as_pin_mut().set_text(&text.to_string())
|
||||
}
|
||||
|
||||
pub fn set_autonormalize(&mut self, auto_normalize: bool) {
|
||||
self.as_pin_mut().set_autonormalize(auto_normalize)
|
||||
}
|
||||
|
||||
pub fn set_book_name(&mut self, book_name: &str) {
|
||||
self.as_pin_mut().set_book_name(&book_name.to_string())
|
||||
}
|
||||
|
||||
pub fn set_intros(&mut self, intros: bool) {
|
||||
self.as_pin_mut().set_intros(intros)
|
||||
}
|
||||
|
||||
/// Set the testament
|
||||
pub fn set_testament(&mut self, testament: u8) {
|
||||
self.inner.pin_mut().set_testament(testament)
|
||||
self.as_pin_mut().set_testament(testament)
|
||||
}
|
||||
|
||||
/// Set the book
|
||||
pub fn set_book(&mut self, book: u8) {
|
||||
self.inner.pin_mut().set_book(book)
|
||||
self.as_pin_mut().set_book(book)
|
||||
}
|
||||
|
||||
/// Set the chapter
|
||||
pub fn set_chapter(&mut self, chapter: u8) {
|
||||
self.inner.pin_mut().set_chapter(chapter)
|
||||
self.as_pin_mut().set_chapter(chapter)
|
||||
}
|
||||
|
||||
/// Set the verse
|
||||
pub fn set_verse(&mut self, verse: u8) {
|
||||
self.inner.pin_mut().set_verse(verse)
|
||||
self.as_pin_mut().set_verse(verse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,203 +185,17 @@ impl Default for VerseKey {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for borrowed VerseKey instances (from modules)
|
||||
pub struct BorrowedVerseKey {
|
||||
ptr: *mut ffi::VerseKey,
|
||||
}
|
||||
|
||||
impl BorrowedVerseKey {
|
||||
/// Create from a raw pointer (unsafe)
|
||||
pub unsafe fn from_ptr(ptr: *mut ffi::VerseKey) -> Option<Self> {
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(Self { ptr })
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the book name
|
||||
pub fn get_book_name(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_book_name() }
|
||||
}
|
||||
|
||||
/// Get the book abbreviation
|
||||
pub fn get_book_abbrev(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_book_abbrev() }
|
||||
}
|
||||
|
||||
/// Get the OSIS book name
|
||||
pub fn get_osis_book_name(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_osis_book_name() }
|
||||
}
|
||||
|
||||
/// Get the OSIS reference
|
||||
pub fn get_osis_ref(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_osis_ref() }
|
||||
}
|
||||
|
||||
/// Get the OSIS reference range text
|
||||
pub fn get_osis_ref_range_text(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_osis_ref_range_text() }
|
||||
}
|
||||
|
||||
/// Get the short range text
|
||||
pub fn get_short_range_text(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_short_range_text() }
|
||||
}
|
||||
|
||||
/// Get the testament number
|
||||
pub fn get_testament(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_testament() }
|
||||
}
|
||||
|
||||
/// Get the maximum testament number
|
||||
pub fn get_testament_max(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_testament_max() }
|
||||
}
|
||||
|
||||
/// Get the book number
|
||||
pub fn get_book(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_book() }
|
||||
}
|
||||
|
||||
/// Get the maximum book number
|
||||
pub fn get_book_max(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_book_max() }
|
||||
}
|
||||
|
||||
/// Get the chapter number
|
||||
pub fn get_chapter(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_chapter() }
|
||||
}
|
||||
|
||||
/// Get the maximum chapter number
|
||||
pub fn get_chapter_max(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_chapter_max() }
|
||||
}
|
||||
|
||||
/// Get the verse number
|
||||
pub fn get_verse(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_verse() }
|
||||
}
|
||||
|
||||
/// Get the maximum verse number
|
||||
pub fn get_verse_max(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_verse_max() }
|
||||
}
|
||||
|
||||
/// Check if auto-normalize is enabled
|
||||
pub fn is_autonormalize(&self) -> bool {
|
||||
unsafe { (&*self.ptr).is_autonormalize() }
|
||||
}
|
||||
|
||||
/// Check if intros are enabled
|
||||
pub fn is_intros(&self) -> bool {
|
||||
unsafe { (&*self.ptr).is_intros() }
|
||||
}
|
||||
|
||||
/// Get the error code
|
||||
pub fn get_error(&self) -> u8 {
|
||||
unsafe { (&*self.ptr).get_error() }
|
||||
}
|
||||
|
||||
/// Get the locale
|
||||
pub fn get_locale(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_locale() }
|
||||
}
|
||||
|
||||
/// Get the range text
|
||||
pub fn get_range_text(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_range_text() }
|
||||
}
|
||||
|
||||
/// Get the short text
|
||||
pub fn get_short_text(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_short_text() }
|
||||
}
|
||||
|
||||
/// Get the text
|
||||
pub fn get_text(&self) -> String {
|
||||
unsafe { (&*self.ptr).get_text() }
|
||||
}
|
||||
|
||||
/// Normalize the key
|
||||
pub fn normalize(&mut self, autocheck: bool) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).normalize(autocheck);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pop an error
|
||||
pub fn pop_error(&mut self) -> u8 {
|
||||
unsafe { std::pin::Pin::new_unchecked(&mut *self.ptr).pop_error() }
|
||||
}
|
||||
|
||||
/// Set auto-normalize
|
||||
pub fn set_autonormalize(&mut self, auto_normalize: bool) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_autonormalize(auto_normalize);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the book name
|
||||
pub fn set_book_name(&mut self, book_name: &str) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_book_name(&book_name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Set intros
|
||||
pub fn set_intros(&mut self, intros: bool) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_intros(intros);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the locale
|
||||
pub fn set_locale(&mut self, locale: &str) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_locale(&locale.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the text
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_text(&text.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the testament
|
||||
pub fn set_testament(&mut self, testament: u8) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_testament(testament);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the book
|
||||
pub fn set_book(&mut self, book: u8) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_book(book);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the chapter
|
||||
pub fn set_chapter(&mut self, chapter: u8) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_chapter(chapter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the verse
|
||||
pub fn set_verse(&mut self, verse: u8) {
|
||||
unsafe {
|
||||
std::pin::Pin::new_unchecked(&mut *self.ptr).set_verse(verse);
|
||||
impl Clone for VerseKey {
|
||||
fn clone(&self) -> Self {
|
||||
let cloned_ptr = ffi::clone_verse_key(self.as_ref());
|
||||
if cloned_ptr.is_null() {
|
||||
panic!("Failed to clone VerseKey");
|
||||
}
|
||||
Self { ptr: cloned_ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BorrowedVerseKey {
|
||||
impl Drop for VerseKey {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
unsafe {
|
||||
@@ -393,6 +205,28 @@ impl Drop for BorrowedVerseKey {
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: BorrowedVerseKey can be sent between threads safely as long as
|
||||
// the underlying SWORD library is thread-safe for reading
|
||||
unsafe impl Send for BorrowedVerseKey {}
|
||||
impl fmt::Debug for VerseKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_valid() {
|
||||
f.debug_struct("VerseKey")
|
||||
.field("text", &self.get_text())
|
||||
.field("osis_ref", &self.get_osis_ref())
|
||||
.finish()
|
||||
} else {
|
||||
f.debug_struct("VerseKey").field("valid", &false).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VerseKey {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_valid() {
|
||||
write!(f, "{}", self.get_text())
|
||||
} else {
|
||||
write!(f, "<invalid>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for VerseKey {}
|
||||
unsafe impl Sync for VerseKey {}
|
||||
|
||||
Reference in New Issue
Block a user