widebible 0.1.3

Rust SDK for WideBible -- Bible verses, books, people, places, cross-references, and study tools via the widebible.com API.
Documentation
use crate::types::*;

const DEFAULT_BASE_URL: &str = "https://widebible.com/api/v1/bible";

/// Async client for the WideBible API.
///
/// Provides access to Bible books, verses, people, places, topics,
/// cross-references, glossary, timeline, and reading plans.
pub struct Client {
    base_url: String,
    http: reqwest::Client,
}

impl Client {
    /// Creates a new client with the default base URL.
    pub fn new() -> Self {
        Self {
            base_url: DEFAULT_BASE_URL.to_string(),
            http: reqwest::Client::new(),
        }
    }

    /// Creates a new client with a custom base URL.
    pub fn with_base_url(base_url: &str) -> Self {
        Self {
            base_url: base_url.to_string(),
            http: reqwest::Client::new(),
        }
    }

    async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T, WideBibleError> {
        let url = format!("{}{}", self.base_url, path);
        let resp = self.http.get(&url).send().await?;
        if !resp.status().is_success() {
            return Err(WideBibleError::Api {
                status: resp.status().as_u16(),
                body: resp.text().await.unwrap_or_default(),
            });
        }
        Ok(resp.json().await?)
    }

    async fn get_with_params<T: serde::de::DeserializeOwned>(
        &self,
        path: &str,
        params: &[(&str, &str)],
    ) -> Result<T, WideBibleError> {
        let url = format!("{}{}", self.base_url, path);
        let resp = self.http.get(&url).query(params).send().await?;
        if !resp.status().is_success() {
            return Err(WideBibleError::Api {
                status: resp.status().as_u16(),
                body: resp.text().await.unwrap_or_default(),
            });
        }
        Ok(resp.json().await?)
    }

    // -- Books ----------------------------------------------------------------

    /// List all 66 books of the Bible.
    pub async fn list_books(&self) -> Result<ListResponse<Book>, WideBibleError> {
        self.get("/books/").await
    }

    /// Get book details by slug (e.g. "genesis", "john").
    pub async fn get_book(&self, slug: &str) -> Result<Book, WideBibleError> {
        self.get(&format!("/books/{}/", slug)).await
    }

    // -- Book Groups ----------------------------------------------------------

    /// List book groups (Pentateuch, Gospels, etc.).
    pub async fn list_book_groups(&self) -> Result<ListResponse<BookGroup>, WideBibleError> {
        self.get("/book-groups/").await
    }

    /// Get book group details by slug.
    pub async fn get_book_group(&self, slug: &str) -> Result<BookGroup, WideBibleError> {
        self.get(&format!("/book-groups/{}/", slug)).await
    }

    // -- Translations ---------------------------------------------------------

    /// List available Bible translations (KJV, ASV, BBE, YLT).
    pub async fn list_translations(&self) -> Result<ListResponse<Translation>, WideBibleError> {
        self.get("/translations/").await
    }

    /// Get translation details by code (e.g. "kjv").
    pub async fn get_translation(&self, code: &str) -> Result<Translation, WideBibleError> {
        self.get(&format!("/translations/{}/", code)).await
    }

    // -- Verses ---------------------------------------------------------------

    /// Get a specific Bible verse by book, chapter, and verse number.
    pub async fn get_verse(
        &self,
        book: &str,
        chapter: u32,
        verse: u32,
        translation: &str,
    ) -> Result<ListResponse<Verse>, WideBibleError> {
        let ch = chapter.to_string();
        let v = verse.to_string();
        self.get_with_params(
            "/verses/",
            &[
                ("book__slug", book),
                ("chapter", &ch),
                ("verse", &v),
                ("translation__code", translation),
            ],
        )
        .await
    }

    /// Search Bible verses by keyword.
    pub async fn search(&self, query: &str) -> Result<ListResponse<Verse>, WideBibleError> {
        self.get_with_params("/verses/", &[("search", query)]).await
    }

    // -- People ---------------------------------------------------------------

    /// List biblical people (prophets, kings, apostles, etc.).
    pub async fn list_people(&self) -> Result<ListResponse<Person>, WideBibleError> {
        self.get("/people/").await
    }

    /// Get a biblical person's details by slug.
    pub async fn get_person(&self, slug: &str) -> Result<Person, WideBibleError> {
        self.get(&format!("/people/{}/", slug)).await
    }

    // -- Places ---------------------------------------------------------------

    /// List biblical places with geocoded coordinates.
    pub async fn list_places(&self) -> Result<ListResponse<Place>, WideBibleError> {
        self.get("/places/").await
    }

    /// Get a biblical place's details by slug.
    pub async fn get_place(&self, slug: &str) -> Result<Place, WideBibleError> {
        self.get(&format!("/places/{}/", slug)).await
    }

    // -- Topics ---------------------------------------------------------------

    /// List thematic verse collections (love, faith, prayer, etc.).
    pub async fn list_topics(&self) -> Result<ListResponse<Topic>, WideBibleError> {
        self.get("/topics/").await
    }

    /// Get topic details and associated verses by slug.
    pub async fn get_topic(&self, slug: &str) -> Result<Topic, WideBibleError> {
        self.get(&format!("/topics/{}/", slug)).await
    }

    // -- Cross-References -----------------------------------------------------

    /// List cross-references between Bible passages.
    pub async fn list_cross_references(
        &self,
    ) -> Result<ListResponse<CrossReference>, WideBibleError> {
        self.get("/cross-references/").await
    }

    // -- Glossary -------------------------------------------------------------

    /// List biblical glossary terms with Hebrew/Greek words.
    pub async fn list_glossary(&self) -> Result<ListResponse<GlossaryTerm>, WideBibleError> {
        self.get("/glossary/").await
    }

    /// Get a glossary term's definition by slug.
    pub async fn get_glossary_term(&self, slug: &str) -> Result<GlossaryTerm, WideBibleError> {
        self.get(&format!("/glossary/{}/", slug)).await
    }

    // -- Timeline -------------------------------------------------------------

    /// List biblical timeline eras (Patriarchs, Exodus, etc.).
    pub async fn list_eras(&self) -> Result<ListResponse<TimelineEra>, WideBibleError> {
        self.get("/timeline-eras/").await
    }

    /// List timeline events, optionally filtered by era slug.
    pub async fn list_events(&self) -> Result<ListResponse<TimelineEvent>, WideBibleError> {
        self.get("/timeline-events/").await
    }

    // -- Reading Plans --------------------------------------------------------

    /// List Bible reading plans.
    pub async fn list_reading_plans(&self) -> Result<ListResponse<ReadingPlan>, WideBibleError> {
        self.get("/reading-plans/").await
    }

    /// Get a reading plan's details and daily entries by slug.
    pub async fn get_reading_plan(&self, slug: &str) -> Result<ReadingPlan, WideBibleError> {
        self.get(&format!("/reading-plans/{}/", slug)).await
    }
}

impl Default for Client {
    fn default() -> Self {
        Self::new()
    }
}