Skip to content

Commit 241576c

Browse files
committed
Rename: collection() requires trait Document
1 parent 0070ab2 commit 241576c

File tree

10 files changed

+140
-48
lines changed

10 files changed

+140
-48
lines changed

typesense/src/client/collection/document.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ pub struct Document<'c, 'n, D = serde_json::Value>
1717
where
1818
D: DeserializeOwned + Serialize + Send + Sync,
1919
{
20-
pub(super) client: &'c Client,
21-
pub(super) collection_name: &'n str,
22-
pub(super) document_id: String,
23-
pub(super) _phantom: std::marker::PhantomData<D>,
20+
client: &'c Client,
21+
collection_name: &'n str,
22+
document_id: String,
23+
_phantom: std::marker::PhantomData<D>,
2424
}
2525

2626
impl<'c, 'n, D> Document<'c, 'n, D>

typesense/src/client/collection/documents.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ pub struct Documents<'c, 'n, D = serde_json::Value>
2525
where
2626
D: DeserializeOwned + Serialize + Send + Sync,
2727
{
28-
pub(super) client: &'c Client,
29-
pub(super) collection_name: &'n str,
30-
pub(super) _phantom: std::marker::PhantomData<D>,
28+
client: &'c Client,
29+
collection_name: &'n str,
30+
_phantom: std::marker::PhantomData<D>,
3131
}
3232

3333
impl<'c, 'n, D> Documents<'c, 'n, D>

typesense/src/client/collection/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ pub struct Collection<'c, 'n, D = serde_json::Value>
1616
where
1717
D: DeserializeOwned + Serialize + Send + Sync,
1818
{
19-
pub(super) client: &'c Client,
20-
pub(super) collection_name: &'n str,
21-
pub(super) _phantom: std::marker::PhantomData<D>,
19+
client: &'c Client,
20+
collection_name: &'n str,
21+
_phantom: std::marker::PhantomData<D>,
2222
}
2323

2424
impl<'c, 'n, D> Collection<'c, 'n, D>

typesense/src/client/mod.rs

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ mod key;
112112
mod keys;
113113
mod multi_search;
114114

115-
use crate::Error;
115+
use crate::{Error, traits::Document};
116116
use collection::Collection;
117117
use collections::Collections;
118118
use key::Key;
@@ -392,7 +392,7 @@ impl Client {
392392
/// # .build()
393393
/// # .unwrap();
394394
/// // Get a typed handle to the "books" collection
395-
/// let books_collection = client.collection_of::<Book>("books");
395+
/// let books_collection = client.collection_named::<Book>("books");
396396
///
397397
/// // Retrieve a single book, it returns `Result<Book, ...>`
398398
/// let book = books_collection.document("123").retrieve().await?;
@@ -403,13 +403,58 @@ impl Client {
403403
/// # }
404404
/// ```
405405
#[inline]
406-
pub fn collection_of<'c, 'n, T>(&'c self, collection_name: &'n str) -> Collection<'c, 'n, T>
406+
pub fn collection_named<'c, 'n, D>(&'c self, collection_name: &'n str) -> Collection<'c, 'n, D>
407407
where
408-
T: DeserializeOwned + Serialize + Send + Sync,
408+
D: DeserializeOwned + Serialize + Send + Sync,
409409
{
410410
Collection::new(self, collection_name)
411411
}
412412

413+
/// Provides access to API endpoints for a specific collection.
414+
///
415+
/// This method returns a `Collection<T>` handle, which is generic over the type of document
416+
/// stored in that collection.
417+
///
418+
/// # Type Parameters
419+
/// * `T` - The type of the documents in the collection. It must be of trait Document.
420+
///
421+
/// # Example: Working with a strongly-typed collection
422+
///
423+
/// When you want to retrieve or search for documents and have them automatically
424+
/// deserialized into your own structs.
425+
/// ```no_run
426+
/// # #[cfg(not(target_family = "wasm"))]
427+
/// # {
428+
/// # use typesense::Client;
429+
/// # use serde::{Serialize, Deserialize};
430+
/// #
431+
/// # #[derive(Serialize, Deserialize, Debug)]
432+
/// # struct Book { id: String, title: String }
433+
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
434+
/// # let client = Client::builder()
435+
/// # .nodes(vec!["http://localhost:8108"])
436+
/// # .api_key("xyz")
437+
/// # .build()
438+
/// # .unwrap();
439+
/// // Get a typed handle to the "books" collection
440+
/// let books_collection = client.collection::<Book>;
441+
///
442+
/// // Retrieve a single book, it returns `Result<Book, ...>`
443+
/// let book = books_collection.document("123").retrieve().await?;
444+
/// println!("Retrieved book: {:?}", book);
445+
/// #
446+
/// # Ok(())
447+
/// # }
448+
/// # }
449+
/// ```
450+
#[inline]
451+
pub fn collection<'c, 'n, D>(&'c self) -> Collection<'c, 'n, D>
452+
where
453+
D: Document + Send + Sync,
454+
{
455+
Collection::new(self, D::COLLECTION_NAME)
456+
}
457+
413458
/// Provides access to API endpoints for a specific collection using schemaless `serde_json::Value` documents.
414459
///
415460
/// This is the simplest way to interact with a collection when you do not need strong typing.
@@ -432,18 +477,18 @@ impl Client {
432477
/// # .api_key("xyz")
433478
/// # .build()
434479
/// # .unwrap();
435-
/// let products_collection = client.collection("products");
480+
/// let products_collection = client.collection_schemaless("products");
436481
/// #
437482
/// # Ok(())
438483
/// # }
439484
/// # }
440485
/// ```
441486
#[inline]
442-
pub fn collection<'c, 'n>(
487+
pub fn collection_schemaless<'c, 'n>(
443488
&'c self,
444489
collection_name: &'n str,
445490
) -> Collection<'c, 'n, serde_json::Value> {
446-
self.collection_of(collection_name)
491+
Collection::new(self, collection_name)
447492
}
448493

449494
/// Provides access to endpoints for managing the collection of API keys.

typesense/src/traits/document.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use serde::{Serialize, de::DeserializeOwned};
99
/// Trait that should implement every struct that wants to be represented as a Typesense
1010
/// Document
1111
pub trait Document: DeserializeOwned + Serialize {
12+
/// Collection name
13+
const COLLECTION_NAME: &'static str;
14+
1215
/// Collection schema associated with the document.
1316
fn collection_schema() -> CollectionSchema;
1417
}

typesense/tests/client/client_test.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async fn test_success_on_first_node() {
6161

6262
let client = get_client(vec![server1.uri()], None);
6363

64-
let result = client.collection("products").retrieve().await;
64+
let result = client.collection_schemaless("products").retrieve().await;
6565

6666
assert!(result.is_ok());
6767
assert_eq!(result.unwrap().name, "products");
@@ -78,7 +78,7 @@ async fn test_failover_to_second_node() {
7878

7979
let client = get_client(vec![server1.uri(), server2.uri()], None);
8080

81-
let result = client.collection("products").retrieve().await;
81+
let result = client.collection_schemaless("products").retrieve().await;
8282
assert!(result.is_ok());
8383

8484
// The first server should have been tried and failed.
@@ -96,7 +96,7 @@ async fn test_nearest_node_is_prioritized() {
9696

9797
let client = get_client(vec![regular_server.uri()], Some(nearest_server.uri()));
9898

99-
let result = client.collection("products").retrieve().await;
99+
let result = client.collection_schemaless("products").retrieve().await;
100100
assert!(result.is_ok());
101101

102102
// Only the nearest node should have received a request.
@@ -113,7 +113,7 @@ async fn test_failover_from_nearest_to_regular_node() {
113113

114114
let client = get_client(vec![regular_server.uri()], Some(nearest_server.uri()));
115115

116-
let result = client.collection("products").retrieve().await;
116+
let result = client.collection_schemaless("products").retrieve().await;
117117
assert!(result.is_ok());
118118

119119
// Nearest node should have failed.
@@ -134,7 +134,13 @@ async fn test_round_robin_failover() {
134134
let client = get_client(vec![server1.uri(), server2.uri(), server3.uri()], None);
135135

136136
// First request should fail over to the third node
137-
assert!(client.collection("products").retrieve().await.is_ok());
137+
assert!(
138+
client
139+
.collection_schemaless("products")
140+
.retrieve()
141+
.await
142+
.is_ok()
143+
);
138144

139145
assert_eq!(server1.received_requests().await.unwrap().len(), 1);
140146
assert_eq!(server2.received_requests().await.unwrap().len(), 1);
@@ -151,7 +157,13 @@ async fn test_round_robin_failover() {
151157
server2.reset().await;
152158
setup_mock_server_ok(&server2, "products").await;
153159

154-
assert!(client.collection("products").retrieve().await.is_ok());
160+
assert!(
161+
client
162+
.collection_schemaless("products")
163+
.retrieve()
164+
.await
165+
.is_ok()
166+
);
155167

156168
// Server 3 was tried first and failed.
157169
assert_eq!(server3.received_requests().await.unwrap().len(), 1);
@@ -179,12 +191,24 @@ async fn test_health_check_and_node_recovery() {
179191
.expect("Failed to create client");
180192

181193
// 1. First request fails over to server2, marking server1 as unhealthy.
182-
assert!(client.collection("products").retrieve().await.is_ok());
194+
assert!(
195+
client
196+
.collection_schemaless("products")
197+
.retrieve()
198+
.await
199+
.is_ok()
200+
);
183201
assert_eq!(server1.received_requests().await.unwrap().len(), 1);
184202
assert_eq!(server2.received_requests().await.unwrap().len(), 1);
185203

186204
// 2. Immediate second request should go directly to server2.
187-
assert!(client.collection("products").retrieve().await.is_ok());
205+
assert!(
206+
client
207+
.collection_schemaless("products")
208+
.retrieve()
209+
.await
210+
.is_ok()
211+
);
188212
assert_eq!(server1.received_requests().await.unwrap().len(), 1); // No new request
189213
assert_eq!(server2.received_requests().await.unwrap().len(), 2); // Got another request
190214

@@ -196,7 +220,13 @@ async fn test_health_check_and_node_recovery() {
196220
setup_mock_server_ok(&server1, "products").await;
197221

198222
// 5. The next request should try server1 again (due to healthcheck expiry) and succeed.
199-
assert!(client.collection("products").retrieve().await.is_ok());
223+
assert!(
224+
client
225+
.collection_schemaless("products")
226+
.retrieve()
227+
.await
228+
.is_ok()
229+
);
200230
assert_eq!(server1.received_requests().await.unwrap().len(), 1); // Server 1 received its first successful req
201231
assert_eq!(server2.received_requests().await.unwrap().len(), 2); // No new request for server 2
202232
}
@@ -210,7 +240,7 @@ async fn test_all_nodes_fail() {
210240

211241
let client = get_client(vec![server1.uri(), server2.uri()], None);
212242

213-
let result = client.collection("products").retrieve().await;
243+
let result = client.collection_schemaless("products").retrieve().await;
214244
assert!(result.is_err());
215245

216246
match result.err().unwrap() {
@@ -233,7 +263,7 @@ async fn test_fail_fast_on_non_retriable_error() {
233263

234264
let client = get_client(vec![server1.uri(), server2.uri()], None);
235265

236-
let result = client.collection("products").retrieve().await;
266+
let result = client.collection_schemaless("products").retrieve().await;
237267
assert!(result.is_err());
238268

239269
// Check that the error is the non-retriable API error.
@@ -264,9 +294,9 @@ async fn test_load_balancing_with_healthy_nodes() {
264294
let client = get_client(vec![server1.uri(), server2.uri(), server3.uri()], None);
265295

266296
// 3. Make three consecutive requests
267-
let result1 = client.collection("products").retrieve().await;
268-
let result2 = client.collection("products").retrieve().await;
269-
let result3 = client.collection("products").retrieve().await;
297+
let result1 = client.collection_schemaless("products").retrieve().await;
298+
let result2 = client.collection_schemaless("products").retrieve().await;
299+
let result3 = client.collection_schemaless("products").retrieve().await;
270300

271301
// 4. Assert all requests were successful
272302
assert!(result1.is_ok());

typesense/tests/client/collections_test.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ async fn logic_test_collections_and_collection_lifecycle() {
3232
assert_eq!(created_collection.name, collection_name);
3333

3434
// --- 2. Retrieve the specific Collection (via `collection`) ---
35-
let retrieve_one_result = client.collection(&collection_name).retrieve().await;
35+
let retrieve_one_result = client
36+
.collection_schemaless(&collection_name)
37+
.retrieve()
38+
.await;
3639
assert!(
3740
retrieve_one_result.is_ok(),
3841
"Failed to retrieve the newly created collection."
@@ -80,7 +83,7 @@ async fn logic_test_collections_and_collection_lifecycle() {
8083
};
8184

8285
let update_result = client
83-
.collection(&collection_name)
86+
.collection_schemaless(&collection_name)
8487
.update(update_schema)
8588
.await;
8689
assert!(update_result.is_ok(), "Failed to update collection");
@@ -94,7 +97,10 @@ async fn logic_test_collections_and_collection_lifecycle() {
9497
);
9598

9699
// --- 6. Verify the update by retrieving the full schema again ---
97-
let retrieve_after_update_result = client.collection(&collection_name).retrieve().await;
100+
let retrieve_after_update_result = client
101+
.collection_schemaless(&collection_name)
102+
.retrieve()
103+
.await;
98104
let retrieved_after_update = retrieve_after_update_result.unwrap();
99105

100106
// Initial fields: name, price. Update: +description, -price. Final fields: name, description.
@@ -126,11 +132,17 @@ async fn logic_test_collections_and_collection_lifecycle() {
126132
);
127133

128134
// --- 7. Delete the Collection (via `collection`) ---
129-
let delete_result = client.collection(&collection_name).delete().await;
135+
let delete_result = client
136+
.collection_schemaless(&collection_name)
137+
.delete()
138+
.await;
130139
assert!(delete_result.is_ok(), "Failed to delete collection");
131140

132141
// --- 8. Verify Deletion ---
133-
let get_after_delete_result = client.collection(&collection_name).retrieve().await;
142+
let get_after_delete_result = client
143+
.collection_schemaless(&collection_name)
144+
.retrieve()
145+
.await;
134146
assert!(
135147
get_after_delete_result.is_err(),
136148
"Collection should not exist after deletion"

0 commit comments

Comments
 (0)