wideholy 0.1.3

Rust SDK for WideHoly -- cross-religion scripture search, verse comparison, religious calendar, and commentary via the wideholy.com API.
Documentation
use crate::types::*;

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

/// Async client for the WideHoly cross-religion hub API.
///
/// Provides access to verse of the day, random verse, cross-religion
/// comparison, search suggestions, topic verses, religious calendar,
/// and commentary across Bible, Quran, Torah, Gita, and Sutra.
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, WideHolyError> {
        let url = format!("{}{}", self.base_url, path);
        let resp = self.http.get(&url).send().await?;
        if !resp.status().is_success() {
            return Err(WideHolyError::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, WideHolyError> {
        let url = format!("{}{}", self.base_url, path);
        let resp = self.http.get(&url).query(params).send().await?;
        if !resp.status().is_success() {
            return Err(WideHolyError::Api {
                status: resp.status().as_u16(),
                body: resp.text().await.unwrap_or_default(),
            });
        }
        Ok(resp.json().await?)
    }

    // -- Verse of the Day -----------------------------------------------------

    /// Get the verse of the day for a religion.
    ///
    /// Returns a curated verse, deterministic per religion per date.
    /// Valid religions: "bible", "quran", "torah", "gita", "sutra".
    pub async fn verse_of_the_day(
        &self,
        religion: &str,
        date: Option<&str>,
    ) -> Result<VerseOfTheDay, WideHolyError> {
        let mut params = vec![("religion", religion)];
        if let Some(d) = date {
            params.push(("date", d));
        }
        self.get_with_params("/verse-of-the-day/", &params).await
    }

    // -- Random Verse ---------------------------------------------------------

    /// Get a random verse, optionally filtered by religion.
    pub async fn random_verse(
        &self,
        religion: Option<&str>,
    ) -> Result<RandomVerse, WideHolyError> {
        match religion {
            Some(r) => self.get_with_params("/random/", &[("religion", r)]).await,
            None => self.get("/random/").await,
        }
    }

    // -- Compare Verses -------------------------------------------------------

    /// Compare two verses side-by-side (same or cross-religion).
    ///
    /// Use "religion:ref" format for references, e.g.:
    /// - `"bible:genesis.1.1"`
    /// - `"quran:al-fatihah.1"`
    pub async fn compare(
        &self,
        ref_a: &str,
        ref_b: &str,
    ) -> Result<CompareResult, WideHolyError> {
        self.get_with_params("/compare/", &[("a", ref_a), ("b", ref_b)])
            .await
    }

    // -- Search Suggest -------------------------------------------------------

    /// Autocomplete suggestions across religions.
    ///
    /// Returns topic, person, and glossary term suggestions matching the query.
    pub async fn search_suggest(
        &self,
        query: &str,
        religion: Option<&str>,
    ) -> Result<SearchSuggestions, WideHolyError> {
        let mut params = vec![("q", query)];
        if let Some(r) = religion {
            params.push(("religion", r));
        }
        self.get_with_params("/search/suggest/", &params).await
    }

    // -- Topic Verses ---------------------------------------------------------

    /// Get verses for a topic across all religions.
    ///
    /// Topics include "love", "creation", "prayer", "mercy", etc.
    pub async fn topic_verses(&self, topic: &str) -> Result<TopicVerses, WideHolyError> {
        self.get(&format!("/topics/{}/verses/", topic)).await
    }

    // -- Cross-Religion Topic -------------------------------------------------

    /// Get cross-religion comparison for a universal spiritual theme.
    pub async fn cross_religion(&self, topic: &str) -> Result<CrossReligion, WideHolyError> {
        self.get(&format!("/cross-religion/{}/", topic)).await
    }

    // -- Religious Calendar ---------------------------------------------------

    /// Get religious calendar events.
    ///
    /// Filter by religion, year, and month.
    pub async fn calendar(
        &self,
        religion: Option<&str>,
        year: Option<u32>,
        month: Option<u32>,
    ) -> Result<CalendarEvents, WideHolyError> {
        let mut params: Vec<(&str, String)> = Vec::new();
        if let Some(r) = religion {
            params.push(("religion", r.to_string()));
        }
        if let Some(y) = year {
            params.push(("year", y.to_string()));
        }
        if let Some(m) = month {
            params.push(("month", m.to_string()));
        }
        let param_refs: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
        self.get_with_params("/calendar/", &param_refs).await
    }

    // -- Commentary -----------------------------------------------------------

    /// Get commentary for a verse.
    ///
    /// Supports Quran (tafsir), Torah (Rashi), and Gita commentaries.
    /// Use "religion:ref" format, e.g. `"quran:al-fatihah.1"`.
    pub async fn commentary(&self, verse_ref: &str) -> Result<CommentaryResult, WideHolyError> {
        self.get(&format!("/commentary/{}/", verse_ref)).await
    }
}

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