use serde::{Deserialize, Serialize};
use std::fmt;
pub const ENVELOPE_VERSION: &str = "1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
#[serde(rename_all = "snake_case")]
pub enum Mode {
General,
News,
Academic,
People,
Deep,
Extract,
Similar,
Scrape,
Scholar,
Patents,
Images,
Places,
Social,
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Mode::General => "general",
Mode::News => "news",
Mode::Academic => "academic",
Mode::People => "people",
Mode::Deep => "deep",
Mode::Extract => "extract",
Mode::Similar => "similar",
Mode::Scrape => "scrape",
Mode::Scholar => "scholar",
Mode::Patents => "patents",
Mode::Images => "images",
Mode::Places => "places",
Mode::Social => "social",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResponseStatus {
Success,
PartialSuccess,
NoResults,
}
impl ResponseStatus {
pub fn as_str(self) -> &'static str {
match self {
Self::Success => "success",
Self::PartialSuccess => "partial_success",
Self::NoResults => "no_results",
}
}
pub fn classify(results_empty: bool, any_failed: bool) -> Self {
match (results_empty, any_failed) {
(false, false) => Self::Success,
(false, true) => Self::PartialSuccess,
(true, _) => Self::NoResults,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FailureCategory {
Auth,
BillingQuota,
RateLimit,
Timeout,
Network,
BadRequest,
Server,
Parse,
Config,
Other,
}
impl FailureCategory {
pub fn as_str(self) -> &'static str {
match self {
Self::Auth => "auth",
Self::BillingQuota => "billing_quota",
Self::RateLimit => "rate_limit",
Self::Timeout => "timeout",
Self::Network => "network",
Self::BadRequest => "bad_request",
Self::Server => "server",
Self::Parse => "parse",
Self::Config => "config",
Self::Other => "other",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderFailure {
pub provider: String,
pub category: FailureCategory,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_status: Option<u16>,
pub code: String,
pub reason: String,
pub retryable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub title: String,
pub url: String,
pub snippet: String,
pub source: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub published: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Answer {
pub provider: String,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResponse {
pub version: String,
pub status: String,
pub query: String,
pub mode: String,
pub results: Vec<SearchResult>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub answers: Vec<Answer>,
pub metadata: ResponseMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResponseMetadata {
pub elapsed_ms: u128,
pub result_count: usize,
pub providers_queried: Vec<String>,
pub providers_failed: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub providers_cancelled: Vec<String>,
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub provider_results: std::collections::BTreeMap<String, usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub cached: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache_age_secs: Option<u64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub provider_failures: Vec<ProviderFailure>,
}
#[derive(Debug, Clone, Default)]
pub struct SearchOpts {
pub include_domains: Vec<String>,
pub exclude_domains: Vec<String>,
pub freshness: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub version: String,
pub status: String,
pub error: ErrorDetail,
}
#[derive(Debug, Serialize)]
pub struct ErrorDetail {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub provider_failures: Vec<ProviderFailure>,
}
#[cfg(test)]
mod tests {
use super::{FailureCategory, ResponseStatus};
#[test]
fn status_ladder() {
assert_eq!(
ResponseStatus::classify(false, false),
ResponseStatus::Success
);
assert_eq!(
ResponseStatus::classify(false, true),
ResponseStatus::PartialSuccess
);
assert_eq!(
ResponseStatus::classify(true, true),
ResponseStatus::NoResults
);
assert_eq!(
ResponseStatus::classify(true, false),
ResponseStatus::NoResults
);
}
#[test]
fn category_serializes_snake_case() {
let j = serde_json::to_string(&FailureCategory::BillingQuota).unwrap();
assert_eq!(j, "\"billing_quota\"");
}
}