use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub status: u16,
pub data: T,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum FieldType {
Text,
Facet,
Bool,
F64,
U64,
I64,
Datetime,
Json,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldConfig {
#[serde(rename = "type")]
pub field_type: FieldType,
#[serde(skip_serializing_if = "Option::is_none")]
pub indexed: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stored: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fast: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fieldnorms: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub precision: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tokenizer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expand_dots: Option<bool>,
}
impl FieldConfig {
#[must_use]
pub fn new(field_type: FieldType) -> Self {
Self {
field_type,
indexed: None,
stored: None,
fast: None,
multi: None,
required: None,
fieldnorms: None,
precision: None,
tokenizer: None,
expand_dots: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LlmProvider {
Anthropic,
Bedrock,
Google,
Llamacpp,
Mistral,
Ollama,
Openai,
Xai,
#[serde(untagged)]
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PromptInstruction {
pub custom_instruction: String,
pub order: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeywordRule {
pub rule_name: String,
pub detect_keywords: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replacement_term: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_instruction: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub empty_state_message: Option<String>,
pub order: i64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct SearchSummaryConfig {
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub character_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_results: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_trim_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub empty_state_message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_prompt_instructions: Option<Vec<PromptInstruction>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keyword_rules: Option<Vec<KeywordRule>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AiConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub search_summary: Option<SearchSummaryConfig>,
pub llm_provider: LlmProvider,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub llm_api_key: Option<String>,
}
impl AiConfig {
#[must_use]
pub fn new(llm_provider: LlmProvider) -> Self {
Self {
search_summary: None,
llm_provider,
llm_region: None,
llm_base_url: None,
llm_api_key: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AiCapabilities {
pub enabled: bool,
pub search_summary_configured: bool,
pub llm_provider_configured: bool,
pub llm_model_configured: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexCapabilities {
pub ai: AiCapabilities,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct IngestionConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub enrichment_pipeline: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct IndexConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_commit_delay: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_language_stemming: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_stop_words: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<HashMap<String, FieldConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_fields: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_decay_field: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub weight_multipliers: Option<HashMap<String, f64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ingestion: Option<IngestionConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ai_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ai: Option<AiConfig>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexListResponse {
pub index_names: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexStats {
pub document_count: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AllIndexStatsResponse {
pub index_count: u64,
pub indices: Vec<HashMap<String, IndexStats>>,
pub total_document_count: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FederationIndexConfig {
pub name: String,
pub weight_multiplier: f32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Federation {
pub name: String,
pub friendly_name: String,
pub created_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created_by: Option<String>,
pub last_modified: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_modified_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub organization_id: Option<String>,
pub index_configurations: Vec<FederationIndexConfig>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FederationRequest {
pub name: String,
pub friendly_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_modified_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_id: Option<String>,
pub index_configurations: Vec<FederationIndexConfig>,
}
impl FederationRequest {
#[must_use]
pub fn new(
name: impl Into<String>,
friendly_name: impl Into<String>,
indices: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Self {
name: name.into(),
friendly_name: friendly_name.into(),
index_configurations: indices
.into_iter()
.map(|name| FederationIndexConfig {
name: name.into(),
weight_multiplier: 1.0,
})
.collect(),
..Self::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FederationStats {
pub federation_name: String,
pub num_docs: u64,
pub indices: Vec<FederationIndexStats>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FederationIndexStats {
pub index_name: String,
pub num_docs: u64,
pub space_usage: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DocumentDeleteResponse {
pub detail: String,
pub num_removed: u64,
}
pub mod permissions {
pub const SEARCH: u64 = 1 << 0;
pub const MODIFY_SYNONYMS: u64 = 1 << 1;
pub const MODIFY_STOP_WORDS: u64 = 1 << 2;
pub const MODIFY_DOCUMENTS: u64 = 1 << 3;
pub const MODIFY_INDEX: u64 = 1 << 4;
pub const MODIFY_AUTH: u64 = 1 << 5;
pub const LLM_RAG_SUMMARIES: u64 = 1 << 6;
pub const LLM_EXPANDED_KEYWORD_SEARCH: u64 = 1 << 7;
pub const READ_ANALYTICS: u64 = 1 << 8;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct AuthKeyPermission(u64);
impl AuthKeyPermission {
pub const READ: Self = Self(permissions::SEARCH);
pub const INGEST: Self = Self(
permissions::SEARCH
| permissions::MODIFY_SYNONYMS
| permissions::MODIFY_STOP_WORDS
| permissions::MODIFY_DOCUMENTS,
);
pub const ADMIN: Self = Self(
permissions::SEARCH
| permissions::MODIFY_SYNONYMS
| permissions::MODIFY_STOP_WORDS
| permissions::MODIFY_DOCUMENTS
| permissions::MODIFY_INDEX
| permissions::MODIFY_AUTH,
);
pub const SUPER_USER: Self = Self(
permissions::SEARCH
| permissions::MODIFY_SYNONYMS
| permissions::MODIFY_STOP_WORDS
| permissions::MODIFY_DOCUMENTS
| permissions::MODIFY_INDEX
| permissions::MODIFY_AUTH
| permissions::LLM_RAG_SUMMARIES
| permissions::LLM_EXPANDED_KEYWORD_SEARCH
| permissions::READ_ANALYTICS,
);
#[must_use]
pub const fn from_bits(bits: u64) -> Self {
Self(bits)
}
#[must_use]
pub const fn bits(self) -> u64 {
self.0
}
#[must_use]
pub const fn contains(self, flags: u64) -> bool {
self.0 & flags == flags
}
#[must_use]
pub const fn intersects(self, flags: u64) -> bool {
self.0 & flags != 0
}
}
impl From<u64> for AuthKeyPermission {
fn from(bits: u64) -> Self {
Self(bits)
}
}
impl From<AuthKeyPermission> for u64 {
fn from(value: AuthKeyPermission) -> Self {
value.0
}
}
impl std::ops::BitOr for AuthKeyPermission {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum AuthKeyStatus {
#[default]
Active,
Inactive,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthKey {
#[serde(rename = "token")]
pub token: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub permissions: AuthKeyPermission,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_indexes: Option<Vec<String>>,
#[serde(default)]
pub status: AuthKeyStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub organization_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub organization_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub application_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub application_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub federation_name: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CreateAuthKeyRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_indexes: Option<Vec<String>>,
pub permissions: AuthKeyPermission,
pub name: String,
pub status: AuthKeyStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub application_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub application_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub federation_name: Option<String>,
}
impl CreateAuthKeyRequest {
#[must_use]
pub fn new(
name: impl Into<String>,
permissions: AuthKeyPermission,
allowed_indexes: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Self {
allowed_indexes: Some(allowed_indexes.into_iter().map(Into::into).collect()),
permissions,
name: name.into(),
status: AuthKeyStatus::Active,
..Self::default()
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateAuthKeyRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_indexes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub permissions: Option<AuthKeyPermission>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<AuthKeyStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub organization_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub application_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub application_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub federation_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HealthCheckResponse {
pub status: u16,
pub data: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeasureStatus {
pub enabled: bool,
}
pub mod event_names {
pub const SDK_INITIALIZED: &str = "sdk_initialized";
pub const SEARCH_REQUESTED: &str = "search_requested";
pub const SEARCH_RESPONSE_RECEIVED: &str = "search_response_received";
pub const SEARCH_COMPLETED: &str = "search_completed";
pub const DOCUMENT_CLICKED: &str = "document_clicked";
pub const API_REQUESTED: &str = "api_requested";
pub const API_QUERIED: &str = "api_queried";
pub const API_SUMMARY_REQUESTED: &str = "api_summary_requested";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum MeasureQueryGranularity {
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
}
impl MeasureQueryGranularity {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Minutes => "minutes",
Self::Hours => "hours",
Self::Days => "days",
Self::Weeks => "weeks",
Self::Months => "months",
Self::Years => "years",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MeasureDashboardParams {
pub organization_id: Option<String>,
pub application_id: Option<String>,
pub index_names: Vec<String>,
pub user_id: Option<String>,
pub user_type: Option<MeasureUserType>,
pub session_id: Option<String>,
pub event_name: Option<String>,
pub date_start: Option<u32>,
pub date_end: Option<u32>,
pub granularity: Option<MeasureQueryGranularity>,
pub rpp: Option<u32>,
pub page: Option<u32>,
}
impl MeasureDashboardParams {
#[must_use]
pub fn to_query_string(&self) -> String {
let mut pairs: Vec<(&str, String)> = Vec::new();
if let Some(v) = &self.organization_id {
pairs.push(("organization_id", v.clone()));
}
if let Some(v) = &self.application_id {
pairs.push(("application_id", v.clone()));
}
if !self.index_names.is_empty() {
pairs.push(("index_names", self.index_names.join("|")));
}
if let Some(v) = &self.user_id {
pairs.push(("user_id", v.clone()));
}
if let Some(v) = self.user_type {
let encoded = match v {
MeasureUserType::Anonymous => "anonymous",
MeasureUserType::Authenticated => "authenticated",
};
pairs.push(("user_type", encoded.to_string()));
}
if let Some(v) = &self.session_id {
pairs.push(("session_id", v.clone()));
}
if let Some(v) = &self.event_name {
pairs.push(("event_name", v.clone()));
}
if let Some(v) = self.date_start {
pairs.push(("date_start", v.to_string()));
}
if let Some(v) = self.date_end {
pairs.push(("date_end", v.to_string()));
}
if let Some(v) = self.granularity {
pairs.push(("granularity", v.as_str().to_string()));
}
if let Some(v) = self.rpp {
pairs.push(("rpp", v.to_string()));
}
if let Some(v) = self.page {
pairs.push(("page", v.to_string()));
}
if pairs.is_empty() {
return String::new();
}
let query: Vec<String> = pairs
.into_iter()
.map(|(key, value)| format!("{key}={}", urlencode(&value)))
.collect();
format!("?{}", query.join("&"))
}
}
fn urlencode(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for byte in value.bytes() {
match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(byte as char);
}
_ => out.push_str(&format!("%{byte:02X}")),
}
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[non_exhaustive]
pub enum MeasureUserType {
Anonymous,
Authenticated,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct MeasureRequestProperties {
#[serde(skip_serializing_if = "Option::is_none")]
pub searchcraft_organization_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub searchcraft_application_id: Option<String>,
pub searchcraft_index_names: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub searchcraft_federation_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_term: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub search_kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ai_provider: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub number_of_documents: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_document_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub document_position: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
}
impl MeasureRequestProperties {
#[must_use]
pub fn new(index_names: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
searchcraft_index_names: index_names.into_iter().map(Into::into).collect(),
..Self::default()
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct MeasureRequestUser {
pub user_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_type: Option<MeasureUserType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub os: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sdk_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sdk_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
}
impl MeasureRequestUser {
#[must_use]
pub fn new(user_id: impl Into<String>) -> Self {
Self {
user_id: user_id.into(),
..Self::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MeasureEvent {
pub event_name: String,
pub properties: MeasureRequestProperties,
pub user: MeasureRequestUser,
}
impl MeasureEvent {
#[must_use]
pub fn new(
event_name: impl Into<String>,
properties: MeasureRequestProperties,
user: MeasureRequestUser,
) -> Self {
Self {
event_name: event_name.into(),
properties,
user,
}
}
}
pub type SynonymsMap = HashMap<String, Vec<String>>;