Skip to main content

pinecone_sdk/pinecone/
data.rs

1use crate::pinecone::PineconeClient;
2use crate::protos::vector_service_client::VectorServiceClient;
3use crate::utils::errors::PineconeError;
4use once_cell::sync::Lazy;
5use tonic::metadata::{Ascii, MetadataValue as TonicMetadataVal};
6use tonic::service::interceptor::InterceptedService;
7use tonic::service::Interceptor;
8use tonic::transport::Channel;
9use tonic::{Request, Status};
10
11use crate::models::{
12    DescribeIndexStatsResponse, FetchResponse, ListResponse, Metadata, Namespace, QueryResponse,
13    SparseValues, UpdateResponse, UpsertResponse, Vector,
14};
15use crate::protos;
16
17#[derive(Debug, Clone)]
18struct ApiKeyInterceptor {
19    api_token: TonicMetadataVal<Ascii>,
20}
21
22impl Interceptor for ApiKeyInterceptor {
23    fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
24        // TODO: replace `api_token` with an `Option`, and do a proper `if_some`.
25        if !self.api_token.is_empty() {
26            request
27                .metadata_mut()
28                .insert("api-key", self.api_token.clone());
29        }
30        Ok(request)
31    }
32}
33
34/// A client for interacting with a Pinecone index.
35#[derive(Debug)]
36pub struct Index {
37    /// The name of the index.
38    host: String,
39    connection: VectorServiceClient<InterceptedService<Channel, ApiKeyInterceptor>>,
40}
41
42impl Index {
43    /// The upsert operation writes vectors into a namespace.
44    /// If a new value is upserted for an existing vector id, it will overwrite the previous value.
45    ///
46    /// ### Arguments
47    /// * `vectors: &[Vector]` - A list of vectors to upsert.
48    /// * `namespace: &Namespace` - The namespace to upsert vectors into. Default is "".
49    ///
50    /// ### Return
51    /// * `Result<UpsertResponse, PineconeError>`
52    ///
53    /// ### Example
54    /// ```no_run
55    /// use pinecone_sdk::models::{Namespace, UpsertResponse, Vector};
56    /// # use pinecone_sdk::utils::errors::PineconeError;
57    ///
58    /// # #[tokio::main]
59    /// # async fn main() -> Result<(), PineconeError>{
60    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
61    ///
62    /// let mut index = pinecone.index("index-host").await?;
63    ///
64    /// let vectors = [Vector {
65    ///     id: "vector-id".to_string(),
66    ///     values: vec![1.0, 2.0, 3.0, 4.0],
67    ///     sparse_values: None,
68    ///     metadata: None,
69    /// }];
70    ///
71    /// // Upsert vectors into the namespace "namespace" in the index
72    /// let response: Result<UpsertResponse, PineconeError> = index.upsert(&vectors, &"namespace".into()).await;
73    /// # Ok(())
74    /// # }
75    /// ```
76    pub async fn upsert(
77        &mut self,
78        vectors: &[Vector],
79        namespace: &Namespace,
80    ) -> Result<UpsertResponse, PineconeError> {
81        let request = protos::UpsertRequest {
82            vectors: vectors.to_vec(),
83            namespace: namespace.name.clone(),
84        };
85
86        let response = self
87            .connection
88            .upsert(request)
89            .await
90            .map_err(|e| PineconeError::DataPlaneError { status: e })?
91            .into_inner();
92
93        Ok(response)
94    }
95
96    /// The list operation lists the IDs of vectors in a single namespace of a serverless index. An optional prefix can be passed to limit the results to IDs with a common prefix.
97    ///
98    /// ### Arguments
99    /// * `namespace: &Namespace` - The namespace to list vectors from. Default is "".
100    /// * `prefix: Option<&str>` - The vector IDs to list, will list all vectors with IDs that have a matching prefix. Default is empty string.
101    /// * `limit: Option<u32>` - The maximum number of vector ids to return. If unspecified, the default limit is 100.
102    /// * `pagination_token: Option<&str>` - The token for paginating through results.
103    ///
104    /// ### Return
105    /// * `Result<ListResponse, PineconeError>`
106    ///
107    /// ### Example
108    /// ```no_run
109    /// use pinecone_sdk::models::{Namespace, ListResponse};
110    /// # use pinecone_sdk::utils::errors::PineconeError;
111    ///
112    /// # #[tokio::main]
113    /// # async fn main() -> Result<(), PineconeError>{
114    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
115    ///
116    /// let mut index = pinecone.index("index-host").await?;
117    ///
118    /// // List all vectors in the namespace "namespace"
119    /// let response: Result<ListResponse, PineconeError> = index.list(&"namespace".into(), None, None, None).await;
120    /// # Ok(())
121    /// # }
122    /// ```
123    pub async fn list(
124        &mut self,
125        namespace: &Namespace,
126        prefix: Option<&str>,
127        limit: Option<u32>,
128        pagination_token: Option<&str>,
129    ) -> Result<ListResponse, PineconeError> {
130        let request = protos::ListRequest {
131            namespace: namespace.name.clone(),
132            prefix: prefix.map(|s| s.to_string()),
133            limit,
134            pagination_token: pagination_token.map(|s| s.to_string()),
135        };
136
137        let response = self
138            .connection
139            .list(request)
140            .await
141            .map_err(|e| PineconeError::DataPlaneError { status: e })?
142            .into_inner();
143
144        Ok(response)
145    }
146
147    /// The describe_index_stats operation returns statistics about the index.
148    ///
149    /// ### Arguments
150    /// * `filter: Option<Metadata>` - An optional filter to specify which vectors to return statistics for. None means no filter will be applied. Note that the filter is only supported by pod indexes.
151    ///
152    /// ### Return
153    /// * `Result<DescribeIndexStatsResponse, PineconeError>`
154    ///
155    /// ### Example
156    /// ```no_run
157    /// use std::collections::BTreeMap;
158    /// use pinecone_sdk::models::{DescribeIndexStatsResponse, Value, Kind, Metadata, Namespace};
159    /// # use pinecone_sdk::utils::errors::PineconeError;
160    ///
161    /// # #[tokio::main]
162    /// # async fn main() -> Result<(), PineconeError>{
163    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
164    ///
165    /// let mut index = pinecone.index("index-host").await?;
166    ///
167    /// // Construct a metadata filter
168    /// let mut fields = BTreeMap::new();
169    /// let kind = Some(Kind::StringValue("value".to_string()));
170    /// fields.insert("field".to_string(), Value { kind });
171    ///
172    /// // Describe the index statistics
173    /// let response: Result<DescribeIndexStatsResponse, PineconeError> = index.describe_index_stats(Some(Metadata { fields })).await;
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub async fn describe_index_stats(
178        &mut self,
179        filter: Option<Metadata>,
180    ) -> Result<DescribeIndexStatsResponse, PineconeError> {
181        let request = protos::DescribeIndexStatsRequest { filter };
182
183        let response = self
184            .connection
185            .describe_index_stats(request)
186            .await
187            .map_err(|e| PineconeError::DataPlaneError { status: e })?
188            .into_inner();
189
190        Ok(response)
191    }
192
193    // Helper function to call query operation
194    async fn query(
195        &mut self,
196        request: protos::QueryRequest,
197    ) -> Result<QueryResponse, PineconeError> {
198        let response = self
199            .connection
200            .query(request)
201            .await
202            .map_err(|e| PineconeError::DataPlaneError { status: e })?
203            .into_inner();
204
205        Ok(response)
206    }
207
208    /// The update operation updates a vector in a namespace. If a value is included, it will overwrite the previous value.
209    /// If a `metadata` filter is included, the values of the fields specified in it will be added or overwrite the previous values.
210    ///
211    /// ### Arguments
212    /// * `id: &str` - The vector's unique ID.
213    /// * `values: Vec<f32>` - The vector data.
214    /// * `sparse_values: Option<SparseValues>` - The sparse vector data.
215    /// * `metadata: Option<MetadataFilter>` - The metadata to set for the vector.
216    /// * `namespace: &Namespace` - The namespace containing the vector to update. Default is "".
217    ///
218    /// ### Return
219    /// * `Result<UpsertResponse, PineconeError>`
220    ///
221    /// ### Example
222    /// ```no_run
223    /// use pinecone_sdk::models::{Namespace, SparseValues, Metadata, UpdateResponse};
224    /// # use pinecone_sdk::utils::errors::PineconeError;
225    ///
226    /// # #[tokio::main]
227    /// # async fn main() -> Result<(), PineconeError>{
228    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
229    ///
230    /// let mut index = pinecone.index("index-host").await?;
231    ///
232    /// // Update the vector with id "vector-id" in the namespace "namespace"
233    /// let response: Result<UpdateResponse, PineconeError> = index.update("vector-id", vec![1.0, 2.0, 3.0, 4.0], None, None, &"namespace".into()).await;
234    /// # Ok(())
235    /// # }
236    /// ```
237    pub async fn update(
238        &mut self,
239        id: &str,
240        values: Vec<f32>,
241        sparse_values: Option<SparseValues>,
242        metadata: Option<Metadata>,
243        namespace: &Namespace,
244    ) -> Result<UpdateResponse, PineconeError> {
245        let request = protos::UpdateRequest {
246            id: id.to_string(),
247            values,
248            sparse_values,
249            set_metadata: metadata,
250            namespace: namespace.name.clone(),
251        };
252
253        let response = self
254            .connection
255            .update(request)
256            .await
257            .map_err(|e| PineconeError::DataPlaneError { status: e })?
258            .into_inner();
259
260        Ok(response)
261    }
262
263    /// The query operation searches a namespace using a query vector. It retrieves the ids of the most similar items in a namespace, along with their similarity scores.
264    ///
265    /// ### Arguments
266    /// * `id: &str` - The id of the query vector.
267    /// * `top_k: u32` - The number of results to return.
268    /// * `namespace: &Namespace` - The namespace to query. Default is "".
269    /// * `filter: Option<Metadata>` - The filter to apply to limit your search by vector metadata.
270    /// * `include_values: Option<bool>` - Indicates whether to include the values of the vectors in the response. Default is false.
271    /// * `include_metadata: Option<bool>` - Indicates whether to include the metadata of the vectors in the response. Default is false.
272    ///
273    /// ### Return
274    /// * `Result<QueryResponse, PineconeError>`
275    ///
276    /// ### Example
277    /// ```no_run
278    /// use pinecone_sdk::models::{Namespace, QueryResponse};
279    /// # use pinecone_sdk::utils::errors::PineconeError;
280    ///
281    /// # #[tokio::main]
282    /// # async fn main() -> Result<(), PineconeError>{
283    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
284    ///
285    /// let mut index = pinecone.index("index-host").await?;
286    ///
287    /// // Query the vector with id "vector-id" in the namespace "namespace"
288    /// let response: Result<QueryResponse, PineconeError> = index.query_by_id("vector-id", 10, &Namespace::default(), None, None, None).await;
289    /// # Ok(())
290    /// # }
291    /// ```
292    pub async fn query_by_id(
293        &mut self,
294        id: &str,
295        top_k: u32,
296        namespace: &Namespace,
297        filter: Option<Metadata>,
298        include_values: Option<bool>,
299        include_metadata: Option<bool>,
300    ) -> Result<QueryResponse, PineconeError> {
301        let request = protos::QueryRequest {
302            id: id.to_string(),
303            top_k,
304            namespace: namespace.name.clone(),
305            filter,
306            include_values: include_values.unwrap_or(false),
307            include_metadata: include_metadata.unwrap_or(false),
308            queries: vec![],
309            vector: vec![],
310            sparse_vector: None,
311        };
312
313        self.query(request).await
314    }
315
316    /// The query operation searches a namespace using a query vector. It retrieves the ids of the most similar items in a namespace, along with their similarity scores.
317    ///
318    /// ### Arguments
319    /// * `vector: Vec<f32>` - The query vector.
320    /// * `sparse_vector: Option<SparseValues>` - Vector sparse data.
321    /// * `top_k: u32` - The number of results to return.
322    /// * `namespace: &Namespace` - The namespace to query. Default is "".
323    /// * `filter: Option<Metadata>` - The filter to apply to limit your search by vector metadata.
324    /// * `include_values: Option<bool>` - Indicates whether to include the values of the vectors in the response. Default is false.
325    /// * `include_metadata: Option<bool>` - Indicates whether to include the metadata of the vectors in the response. Default is false.
326    ///
327    /// ### Return
328    /// * `Result<QueryResponse, PineconeError>`
329    ///
330    /// ### Example
331    /// ```no_run
332    /// use pinecone_sdk::models::{Namespace, QueryResponse};
333    /// # use pinecone_sdk::utils::errors::PineconeError;
334    ///
335    /// # #[tokio::main]
336    /// # async fn main() -> Result<(), PineconeError>{
337    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
338    ///
339    /// let mut index = pinecone.index("index-host").await?;
340    ///
341    /// let vector = vec![1.0, 2.0, 3.0, 4.0];
342    ///
343    /// // Query the vector in the default namespace
344    /// let response: Result<QueryResponse, PineconeError> = index.query_by_value(vector, None, 10, &Namespace::default(), None, None, None).await;
345    /// # Ok(())
346    /// # }
347    /// ```
348    pub async fn query_by_value(
349        &mut self,
350        vector: Vec<f32>,
351        sparse_vector: Option<SparseValues>,
352        top_k: u32,
353        namespace: &Namespace,
354        filter: Option<Metadata>,
355        include_values: Option<bool>,
356        include_metadata: Option<bool>,
357    ) -> Result<QueryResponse, PineconeError> {
358        let request = protos::QueryRequest {
359            id: "".to_string(),
360            top_k,
361            namespace: namespace.name.clone(),
362            filter,
363            include_values: include_values.unwrap_or(false),
364            include_metadata: include_metadata.unwrap_or(false),
365            queries: vec![],
366            vector,
367            sparse_vector,
368        };
369
370        self.query(request).await
371    }
372
373    /// The delete_by_id operation deletes vectors by ID from a namespace.
374    ///
375    /// ### Arguments
376    /// * `ids: &[&str]` - List of IDs of vectors to be deleted.
377    /// * `namespace: &Namespace` - The namespace to delete vectors from. Default is "".
378    ///
379    /// ### Return
380    /// * `Result<(), PineconeError>`
381    ///
382    /// ### Example
383    /// ```no_run
384    /// use pinecone_sdk::models::Namespace;
385    /// # use pinecone_sdk::utils::errors::PineconeError;
386    ///
387    /// # #[tokio::main]
388    /// # async fn main() -> Result<(), PineconeError>{
389    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
390    ///
391    /// let mut index = pinecone.index("index-host").await?;
392    ///
393    /// let ids = ["vector-id"];
394    ///
395    /// // Delete vectors from the namespace "namespace" that have the ids in the list
396    /// let response: Result<(), PineconeError> = index.delete_by_id(&ids, &"namespace".into()).await;
397    /// # Ok(())
398    /// # }
399    /// ```
400    pub async fn delete_by_id(
401        &mut self,
402        ids: &[&str],
403        namespace: &Namespace,
404    ) -> Result<(), PineconeError> {
405        let ids = ids.iter().map(|id| id.to_string()).collect::<Vec<String>>();
406        let request = protos::DeleteRequest {
407            ids,
408            delete_all: false,
409            namespace: namespace.name.clone(),
410            filter: None,
411        };
412
413        self.delete(request).await
414    }
415
416    /// The delete_all operation deletes all vectors from a namespace.
417    ///
418    /// ### Arguments
419    /// * `namespace: &Namespace` - The namespace to delete vectors from. Default is "".
420    ///
421    /// ### Return
422    /// * `Result<(), PineconeError>`
423    ///
424    /// ### Example
425    /// ```no_run
426    /// use pinecone_sdk::models::Namespace;
427    /// # use pinecone_sdk::utils::errors::PineconeError;
428    ///
429    /// # #[tokio::main]
430    /// # async fn main() -> Result<(), PineconeError>{
431    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
432    ///
433    /// let mut index = pinecone.index("index-host").await?;
434    ///
435    /// // Delete all vectors from the namespace "namespace"
436    /// let response: Result<(), PineconeError> = index.delete_all(&"namespace".into()).await;
437    /// # Ok(())
438    /// # }
439    /// ```
440    pub async fn delete_all(&mut self, namespace: &Namespace) -> Result<(), PineconeError> {
441        let request = protos::DeleteRequest {
442            ids: vec![],
443            delete_all: true,
444            namespace: namespace.name.clone(),
445            filter: None,
446        };
447
448        self.delete(request).await
449    }
450
451    /// The delete_by_filter operation deletes the vectors from a namespace that satisfy the filter.
452    ///
453    /// ### Arguments
454    /// * `filter: Metadata` - The filter to specify which vectors to delete.
455    /// * `namespace: &Namespace` - The namespace to delete vectors from. Default is "".
456    ///
457    /// ### Return
458    /// * `Result<(), PineconeError>`
459    ///
460    /// ### Example
461    /// ```no_run
462    /// use std::collections::BTreeMap;
463    /// use pinecone_sdk::models::{Metadata, Value, Kind, Namespace};
464    /// # use pinecone_sdk::utils::errors::PineconeError;
465    ///
466    /// # #[tokio::main]
467    /// # async fn main() -> Result<(), PineconeError>{
468    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
469    ///
470    /// let mut index = pinecone.index("index-host").await?;
471    ///
472    /// // Construct a metadata filter
473    /// let mut fields = BTreeMap::new();
474    /// let kind = Some(Kind::StringValue("value".to_string()));
475    /// fields.insert("field".to_string(), Value { kind });
476    ///
477    /// // Delete vectors from the namespace "namespace" that satisfy the filter
478    /// let response: Result<(), PineconeError> = index.delete_by_filter(Metadata { fields }, &"namespace".into()).await;
479    /// # Ok(())
480    /// # }
481    /// ```
482    pub async fn delete_by_filter(
483        &mut self,
484        filter: Metadata,
485        namespace: &Namespace,
486    ) -> Result<(), PineconeError> {
487        let request = protos::DeleteRequest {
488            ids: vec![],
489            delete_all: false,
490            namespace: namespace.name.clone(),
491            filter: Some(filter),
492        };
493
494        self.delete(request).await
495    }
496
497    // Helper function to call delete operation
498    async fn delete(&mut self, request: protos::DeleteRequest) -> Result<(), PineconeError> {
499        let _ = self
500            .connection
501            .delete(request)
502            .await
503            .map_err(|e| PineconeError::DataPlaneError { status: e })?;
504
505        Ok(())
506    }
507
508    /// The fetch operation retrieves vectors by ID from a namespace.
509    ///
510    /// ### Arguments
511    /// * `ids: &[&str]` - The ids of vectors to fetch.
512    /// * `namespace: &Namespace` - The namespace to fetch vectors from. Default is "".
513    ///
514    /// ### Return
515    /// * `Result<FetchResponse, PineconeError>`
516    ///
517    /// ### Example
518    /// ```no_run
519    /// use std::collections::BTreeMap;
520    /// use pinecone_sdk::models::{FetchResponse, Metadata, Value, Kind};
521    /// # use pinecone_sdk::utils::errors::PineconeError;
522    ///
523    /// # #[tokio::main]
524    /// # async fn main() -> Result<(), PineconeError>{
525    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
526    ///
527    /// let mut index = pinecone.index("index-host").await?;
528    ///
529    /// let vectors = &["1", "2"];
530    ///
531    /// // Fetch vectors from the default namespace that have the ids in the list
532    /// let response: Result<FetchResponse, PineconeError> = index.fetch(vectors, &Default::default()).await;
533    /// Ok(())
534    /// }
535    /// ```
536    pub async fn fetch(
537        &mut self,
538        ids: &[&str],
539        namespace: &Namespace,
540    ) -> Result<FetchResponse, PineconeError> {
541        let ids = ids.iter().map(|id| id.to_string()).collect::<Vec<String>>();
542        let request = protos::FetchRequest {
543            ids,
544            namespace: namespace.name.clone(),
545        };
546
547        let response = self
548            .connection
549            .fetch(request)
550            .await
551            .map_err(|e| PineconeError::DataPlaneError { status: e })?
552            .into_inner();
553
554        Ok(response)
555    }
556}
557
558impl PineconeClient {
559    /// Match the scheme in a host string.
560    ///
561    /// ### Arguments
562    /// * `host: &str` - The host string to match.
563    ///
564    /// ### Return
565    /// * `bool` - True if the host string contains a scheme, false otherwise.
566    fn has_scheme(host: &str) -> bool {
567        static RE: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"^[a-zA-Z]+://").unwrap());
568        RE.is_match(host)
569    }
570
571    /// Match the port in a host string.
572    ///
573    /// ### Arguments
574    /// * `host: &str` - The host string to match.
575    ///
576    /// ### Return
577    /// * `bool` - True if the host string contains a port, false otherwise.
578    fn has_port(host: &str) -> bool {
579        static RE: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r":\d+$").unwrap());
580        RE.is_match(host)
581    }
582
583    /// Target an index for data operations.
584    ///
585    /// ### Arguments
586    /// * `host: &str` - The host of the index to target. If the host does not contain a scheme, it will default to `https://`. If the host does not contain a port, it will default to `443`.
587    ///
588    /// ### Return
589    /// * `Result<Index, PineconeError>`
590    ///
591    /// ### Example
592    ///
593    /// ```no_run
594    /// # use pinecone_sdk::utils::errors::PineconeError;
595    ///
596    /// # #[tokio::main]
597    /// # async fn main() -> Result<(), PineconeError>{
598    /// let pinecone = pinecone_sdk::pinecone::default_client()?;
599    ///
600    /// let index = pinecone.index("index-host").await?;
601    /// # Ok(())
602    /// # }
603    /// ```
604    pub async fn index(&self, host: &str) -> Result<Index, PineconeError> {
605        let endpoint = host.to_string();
606
607        let endpoint = if PineconeClient::has_scheme(&endpoint) {
608            endpoint
609        } else {
610            format!("https://{}", endpoint)
611        };
612
613        let endpoint = if PineconeClient::has_port(&endpoint) {
614            endpoint
615        } else {
616            format!("{}:443", endpoint)
617        };
618
619        let index = Index {
620            host: endpoint.clone(),
621            connection: self.new_index_connection(endpoint).await?,
622        };
623
624        Ok(index)
625    }
626
627    // Helper function to create a new index connection
628    async fn new_index_connection(
629        &self,
630        host: String,
631    ) -> Result<VectorServiceClient<InterceptedService<Channel, ApiKeyInterceptor>>, PineconeError>
632    {
633        let tls_config = tonic::transport::ClientTlsConfig::default();
634
635        // connect to server
636        let endpoint = Channel::from_shared(host)
637            .map_err(|e| PineconeError::ConnectionError { source: e.into() })?
638            .tls_config(tls_config)
639            .map_err(|e| PineconeError::ConnectionError { source: e.into() })?;
640
641        let channel = endpoint
642            .connect()
643            .await
644            .map_err(|e| PineconeError::ConnectionError { source: e.into() })?;
645
646        // add api key in metadata through interceptor
647        let token: TonicMetadataVal<_> = self.api_key.parse().unwrap();
648        let add_api_key_interceptor = ApiKeyInterceptor { api_token: token };
649        let inner = VectorServiceClient::with_interceptor(channel, add_api_key_interceptor);
650
651        Ok(inner)
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658    use crate::pinecone::default_client;
659    use httpmock::prelude::*;
660
661    #[tokio::test]
662    async fn test_index_full_endpoint() {
663        let server = MockServer::start();
664
665        // server url contains scheme and port
666        let _mock = server.mock(|_when, then| {
667            then.status(200);
668        });
669
670        let pinecone = default_client().expect("Failed to create Pinecone instance");
671
672        let index = pinecone.index(server.base_url().as_str()).await.unwrap();
673
674        assert_eq!(index.host, server.base_url());
675    }
676
677    #[tokio::test]
678    async fn test_index_no_scheme() {
679        let server = MockServer::start();
680
681        // server url contains no scheme
682        let _mock = server.mock(|_when, then| {
683            then.status(200);
684        });
685
686        let pinecone = default_client().expect("Failed to create Pinecone instance");
687
688        let addr = server.address().to_string();
689
690        let _index = pinecone
691            .index(addr.as_str())
692            .await
693            .expect_err("Expected connection error");
694    }
695
696    #[tokio::test]
697    async fn test_index_no_port() {
698        let server = MockServer::start();
699
700        // server url contains no port
701        let _mock = server.mock(|_when, then| {
702            then.status(200);
703        });
704
705        let pinecone = default_client().expect("Failed to create Pinecone instance");
706
707        let scheme_host = format!("http://{}", server.host());
708
709        let _index = pinecone
710            .index(scheme_host.as_str())
711            .await
712            .expect_err("Expected connection error");
713    }
714
715    #[tokio::test]
716    async fn test_index_no_scheme_no_port() {
717        let server = MockServer::start();
718
719        // server url contains no scheme and no port
720        let _mock = server.mock(|_when, then| {
721            then.status(200);
722        });
723
724        let pinecone = default_client().expect("Failed to create Pinecone instance");
725
726        let host = server.host();
727
728        let _index = pinecone
729            .index(host.as_str())
730            .await
731            .expect_err("Expected connection error");
732    }
733}