use crate::error::LingerError;
use crate::RequestId;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateVectorStoreRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub file_ids: Vec<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub metadata: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreRequest {
pub fn builder() -> CreateVectorStoreRequestBuilder {
CreateVectorStoreRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateVectorStoreRequestBuilder {
name: Option<String>,
file_ids: Vec<String>,
metadata: BTreeMap<String, String>,
extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreRequestBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
self.file_ids.push(file_id.into());
self
}
pub fn file_ids(mut self, file_ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.file_ids = file_ids.into_iter().map(Into::into).collect();
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateVectorStoreRequest, LingerError> {
validate_optional_string("name", self.name.as_deref())?;
for file_id in &self.file_ids {
if file_id.trim().is_empty() {
return Err(LingerError::invalid_config(
"file_ids must not contain empty values",
));
}
}
for key in self.metadata.keys() {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"metadata keys must not be empty",
));
}
}
Ok(CreateVectorStoreRequest {
name: self.name,
file_ids: self.file_ids,
metadata: self.metadata,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ModifyVectorStoreRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub metadata: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_after: Option<Value>,
}
impl ModifyVectorStoreRequest {
pub fn builder() -> ModifyVectorStoreRequestBuilder {
ModifyVectorStoreRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModifyVectorStoreRequestBuilder {
name: Option<String>,
metadata: BTreeMap<String, String>,
expires_after: Option<Value>,
}
impl ModifyVectorStoreRequestBuilder {
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
pub fn expires_after(mut self, expires_after: Value) -> Self {
self.expires_after = Some(expires_after);
self
}
pub fn build(self) -> Result<ModifyVectorStoreRequest, LingerError> {
validate_optional_string("name", self.name.as_deref())?;
validate_metadata(&self.metadata)?;
validate_optional_json_value("expires_after", self.expires_after.as_ref())?;
Ok(ModifyVectorStoreRequest {
name: self.name,
metadata: self.metadata,
expires_after: self.expires_after,
})
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateVectorStoreFileRequest {
pub file_id: String,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreFileRequest {
pub fn builder() -> CreateVectorStoreFileRequestBuilder {
CreateVectorStoreFileRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateVectorStoreFileRequestBuilder {
file_id: Option<String>,
extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreFileRequestBuilder {
pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
self.file_id = Some(file_id.into());
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateVectorStoreFileRequest, LingerError> {
Ok(CreateVectorStoreFileRequest {
file_id: required_string("file_id", self.file_id)?,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
#[non_exhaustive]
pub struct ModifyVectorStoreFileRequest {
pub attributes: BTreeMap<String, Value>,
}
impl ModifyVectorStoreFileRequest {
pub fn builder() -> ModifyVectorStoreFileRequestBuilder {
ModifyVectorStoreFileRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModifyVectorStoreFileRequestBuilder {
attributes: BTreeMap<String, Value>,
}
impl ModifyVectorStoreFileRequestBuilder {
pub fn attribute(mut self, key: impl Into<String>, value: Value) -> Self {
self.attributes.insert(key.into(), value);
self
}
pub fn attributes(mut self, attributes: BTreeMap<String, Value>) -> Self {
self.attributes = attributes;
self
}
pub fn build(self) -> Result<ModifyVectorStoreFileRequest, LingerError> {
validate_attributes(&self.attributes, true)?;
Ok(ModifyVectorStoreFileRequest {
attributes: self.attributes,
})
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateVectorStoreFileBatchRequest {
pub file_ids: Vec<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreFileBatchRequest {
pub fn builder() -> CreateVectorStoreFileBatchRequestBuilder {
CreateVectorStoreFileBatchRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateVectorStoreFileBatchRequestBuilder {
file_ids: Vec<String>,
extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreFileBatchRequestBuilder {
pub fn file_id(mut self, file_id: impl Into<String>) -> Self {
self.file_ids.push(file_id.into());
self
}
pub fn file_ids(mut self, file_ids: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.file_ids = file_ids.into_iter().map(Into::into).collect();
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateVectorStoreFileBatchRequest, LingerError> {
validate_non_empty_values("file_ids", &self.file_ids)?;
Ok(CreateVectorStoreFileBatchRequest {
file_ids: self.file_ids,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Serialize, PartialEq)]
#[non_exhaustive]
pub struct CreateVectorStoreSearchRequest {
pub query: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_num_results: Option<u32>,
#[serde(rename = "filters", skip_serializing_if = "Option::is_none")]
pub filter: Option<Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreSearchRequest {
pub fn builder() -> CreateVectorStoreSearchRequestBuilder {
CreateVectorStoreSearchRequestBuilder::default()
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct CreateVectorStoreSearchRequestBuilder {
query: Option<String>,
max_num_results: Option<u32>,
filter: Option<Value>,
extra: BTreeMap<String, Value>,
}
impl CreateVectorStoreSearchRequestBuilder {
pub fn query(mut self, query: impl Into<String>) -> Self {
self.query = Some(query.into());
self
}
pub fn max_num_results(mut self, max_num_results: u32) -> Self {
self.max_num_results = Some(max_num_results);
self
}
pub fn filter(mut self, filter: Value) -> Self {
self.filter = Some(filter);
self
}
pub fn extra(mut self, name: impl Into<String>, value: Value) -> Self {
self.extra.insert(name.into(), value);
self
}
pub fn build(self) -> Result<CreateVectorStoreSearchRequest, LingerError> {
let query = required_string("query", self.query)?;
if self
.max_num_results
.is_some_and(|value| !(1..=50).contains(&value))
{
return Err(LingerError::invalid_config(
"max_num_results must be between 1 and 50",
));
}
if self.filter.as_ref().is_some_and(Value::is_null) {
return Err(LingerError::invalid_config("filters must not be null"));
}
validate_extra_fields(&self.extra)?;
Ok(CreateVectorStoreSearchRequest {
query,
max_num_results: self.max_num_results,
filter: self.filter,
extra: self.extra,
})
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStore {
pub id: String,
pub object: String,
pub created_at: u64,
#[serde(default)]
pub name: Option<String>,
#[serde(default, alias = "usage_bytes")]
pub bytes: u64,
pub file_counts: VectorStoreFileCounts,
pub status: String,
#[serde(default)]
pub expires_after: Option<Value>,
#[serde(default)]
pub expires_at: Option<u64>,
#[serde(default)]
pub last_active_at: Option<u64>,
#[serde(default)]
pub metadata: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStore {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct VectorStoreFileCounts {
pub in_progress: u64,
pub completed: u64,
pub failed: u64,
pub cancelled: u64,
pub total: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreFile {
pub id: String,
pub object: String,
pub created_at: u64,
pub vector_store_id: String,
pub status: String,
#[serde(default)]
pub last_error: Option<Value>,
#[serde(default)]
pub usage_bytes: u64,
#[serde(default)]
pub chunking_strategy: Option<Value>,
#[serde(default)]
pub attributes: BTreeMap<String, Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreFile {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreFilePage {
pub object: String,
#[serde(default)]
pub data: Vec<VectorStoreFile>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreFilePage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreFileContent {
#[serde(default)]
pub r#type: Option<String>,
#[serde(default)]
pub text: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreFileContentPage {
pub object: String,
#[serde(default)]
pub data: Vec<VectorStoreFileContent>,
#[serde(default)]
pub has_more: bool,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreFileContentPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreSearchResult {
pub file_id: String,
#[serde(default)]
pub filename: Option<String>,
#[serde(default)]
pub score: Option<f64>,
#[serde(default)]
pub content: Vec<Value>,
#[serde(default)]
pub attributes: BTreeMap<String, Value>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreSearchPage {
pub object: String,
#[serde(default)]
pub data: Vec<VectorStoreSearchResult>,
pub has_more: bool,
#[serde(default)]
pub next_page: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreSearchPage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct VectorStoreFileDeletion {
pub id: String,
pub object: String,
pub deleted: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreFileDeletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStoreFileBatch {
pub id: String,
pub object: String,
pub created_at: u64,
pub vector_store_id: String,
pub status: String,
pub file_counts: VectorStoreFileCounts,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreFileBatch {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[non_exhaustive]
pub struct VectorStorePage {
pub object: String,
#[serde(default)]
pub data: Vec<VectorStore>,
#[serde(default)]
pub first_id: Option<String>,
#[serde(default)]
pub last_id: Option<String>,
pub has_more: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStorePage {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct VectorStoreDeletion {
pub id: String,
pub object: String,
pub deleted: bool,
#[serde(skip)]
request_id: Option<RequestId>,
}
impl VectorStoreDeletion {
pub(crate) fn with_request_id(mut self, request_id: Option<RequestId>) -> Self {
self.request_id = request_id;
self
}
pub fn request_id(&self) -> Option<&RequestId> {
self.request_id.as_ref()
}
}
fn validate_optional_string(name: &str, value: Option<&str>) -> Result<(), LingerError> {
if value.is_some_and(|value| value.trim().is_empty()) {
return Err(LingerError::invalid_config(format!(
"{name} must not be empty"
)));
}
Ok(())
}
fn validate_metadata(metadata: &BTreeMap<String, String>) -> Result<(), LingerError> {
for key in metadata.keys() {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"metadata keys must not be empty",
));
}
}
Ok(())
}
fn validate_optional_json_value(name: &str, value: Option<&Value>) -> Result<(), LingerError> {
if value.is_some_and(Value::is_null) {
return Err(LingerError::invalid_config(format!(
"{name} must not be null"
)));
}
Ok(())
}
fn validate_attributes(
attributes: &BTreeMap<String, Value>,
require_non_empty: bool,
) -> Result<(), LingerError> {
if require_non_empty && attributes.is_empty() {
return Err(LingerError::invalid_config("attributes are required"));
}
for (key, value) in attributes {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"attribute names must not be empty",
));
}
if !(value.is_string() || value.is_number() || value.is_boolean()) {
return Err(LingerError::invalid_config(format!(
"attribute {key} must be a string, number, or boolean"
)));
}
}
Ok(())
}
fn required_string(name: &str, value: Option<String>) -> Result<String, LingerError> {
value
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| LingerError::invalid_config(format!("{name} is required")))
}
fn validate_non_empty_values(name: &str, values: &[String]) -> Result<(), LingerError> {
if values.is_empty() {
return Err(LingerError::invalid_config(format!("{name} is required")));
}
if values.iter().any(|value| value.trim().is_empty()) {
return Err(LingerError::invalid_config(format!(
"{name} must not contain empty values"
)));
}
Ok(())
}
fn validate_extra_fields(extra: &BTreeMap<String, Value>) -> Result<(), LingerError> {
for (key, value) in extra {
if key.trim().is_empty() {
return Err(LingerError::invalid_config(
"extra field names must not be empty",
));
}
if value.is_null() {
return Err(LingerError::invalid_config(format!(
"extra field {key} must not be null"
)));
}
}
Ok(())
}