Skip to content

Commit 80e4556

Browse files
committed
feat: update typed client methods for improved type safety and validation
1 parent 3bc0228 commit 80e4556

12 files changed

+122
-43
lines changed

src/Example/Program.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static async Task Main()
5656

5757
WeaviateClient weaviate = await Connect.Local();
5858

59-
var collection = weaviate.Collections.Use("Cat");
59+
var collection = await weaviate.Collections.Use<Cat>("Cat");
6060

6161
// Should throw CollectionNotFound
6262
try
@@ -83,11 +83,11 @@ static async Task Main()
8383
{
8484
Name = "Cat",
8585
Description = "Lots of Cats of multiple breeds",
86-
Properties = [.. Property.FromClass<Cat>()],
86+
Properties = Property.FromClass<Cat>(),
8787
VectorConfig = new VectorConfig("default", new Vectorizer.Text2VecWeaviate()),
8888
};
8989

90-
collection = await weaviate.Collections.Create(catCollection);
90+
collection = await weaviate.Collections.Create<Cat>(catCollection);
9191

9292
await foreach (var c in weaviate.Collections.List())
9393
{
@@ -116,7 +116,7 @@ static async Task Main()
116116
var result = await collection.Query.FetchObjects(limit: 250);
117117
var retrieved = result.Objects.ToList();
118118
Console.WriteLine("Cats retrieved: " + retrieved.Count());
119-
var sum = retrieved.Sum(c => c.As<Cat>()?.Counter ?? 0);
119+
var sum = retrieved.Sum(c => c.Object?.Counter ?? 0);
120120

121121
// Delete object
122122
var firstObj = retrieved.First();
@@ -161,7 +161,7 @@ static async Task Main()
161161
returnMetadata: MetadataOptions.Score | MetadataOptions.Distance
162162
);
163163

164-
foreach (var cat in queryNearVector.Objects.Select(o => o.As<Cat>()))
164+
foreach (var cat in queryNearVector.Objects.Select(o => o.Object))
165165
{
166166
// Console.WriteLine(
167167
// JsonSerializer.Serialize(cat, new JsonSerializerOptions { WriteIndented = true })
@@ -174,7 +174,7 @@ static async Task Main()
174174
Console.WriteLine("Using collection iterator:");
175175

176176
// Cursor API demo
177-
var objects = collection.Iterator().Select(o => o.As<Cat>());
177+
var objects = collection.Iterator().Select(o => o.Object);
178178
var sumWithIterator = await objects.SumAsync(c => c!.Counter);
179179

180180
// Print all cats found

src/Weaviate.Client.Tests/Integration/TypedClientIntegrationTests.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public async Task Use_WithValidationType_Compatible_Succeeds()
5757
var collection = await CollectionFactory<Article>(_articleConfig);
5858

5959
// Act
60-
var typedClient = await collection.Use<Article>(
60+
var typedClient = await collection.AsTyped<Article>(
6161
validateType: true,
6262
cancellationToken: TestContext.Current.CancellationToken
6363
);
@@ -77,7 +77,7 @@ public async Task Use_WithValidationType_IncompatibleType_ThrowsException()
7777
// Act & Assert
7878
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
7979
{
80-
await collection.Use<IncompatibleArticle>(
80+
await collection.AsTyped<IncompatibleArticle>(
8181
validateType: true,
8282
cancellationToken: TestContext.Current.CancellationToken
8383
);
@@ -94,7 +94,7 @@ public async Task Use_WithoutValidation_AlwaysSucceeds()
9494
var collection = await CollectionFactory<Article>(_articleConfig);
9595

9696
// Act - even with incompatible type, should not throw when validation is disabled
97-
var typedClient = await collection.Use<IncompatibleArticle>(
97+
var typedClient = await collection.AsTyped<IncompatibleArticle>(
9898
validateType: false,
9999
cancellationToken: TestContext.Current.CancellationToken
100100
);
@@ -109,7 +109,7 @@ public async Task ValidateType_OnTypedCollectionClient_ReturnsValidationResult()
109109
{
110110
// Arrange
111111
var collection = await CollectionFactory<Article>(_articleConfig);
112-
var typedClient = await collection.Use<Article>(
112+
var typedClient = await collection.AsTyped<Article>(
113113
validateType: false,
114114
cancellationToken: TestContext.Current.CancellationToken
115115
);
@@ -130,7 +130,7 @@ public async Task ValidateType_WithIncompatibleType_ReturnsErrors()
130130
{
131131
// Arrange
132132
var collection = await CollectionFactory<Article>(_articleConfig);
133-
var typedClient = await collection.Use<IncompatibleArticle>(
133+
var typedClient = await collection.AsTyped<IncompatibleArticle>(
134134
validateType: false, // Skip validation on construction
135135
cancellationToken: TestContext.Current.CancellationToken
136136
);
@@ -193,7 +193,7 @@ public async Task TypedWorkflow_Insert_SingleObject_Succeeds()
193193
{
194194
// Arrange
195195
var collection = await CollectionFactory<Article>(_articleConfig);
196-
var typedClient = await collection.Use<Article>(
196+
var typedClient = await collection.AsTyped<Article>(
197197
cancellationToken: TestContext.Current.CancellationToken
198198
);
199199

@@ -220,7 +220,7 @@ public async Task TypedWorkflow_Insert_WithSpecificId_Succeeds()
220220
{
221221
// Arrange
222222
var collection = await CollectionFactory<Article>(_articleConfig);
223-
var typedClient = await collection.Use<Article>(
223+
var typedClient = await collection.AsTyped<Article>(
224224
cancellationToken: TestContext.Current.CancellationToken
225225
);
226226

@@ -249,7 +249,7 @@ public async Task TypedWorkflow_InsertMany_MultipleObjects_Succeeds()
249249
{
250250
// Arrange
251251
var collection = await CollectionFactory<Article>(_articleConfig);
252-
var typedClient = await collection.Use<Article>(
252+
var typedClient = await collection.AsTyped<Article>(
253253
cancellationToken: TestContext.Current.CancellationToken
254254
);
255255

@@ -296,7 +296,7 @@ public async Task TypedWorkflow_Query_FetchObjects_ReturnsTypedResults()
296296
{
297297
// Arrange
298298
var collection = await CollectionFactory<Article>(_articleConfig);
299-
var typedClient = await collection.Use<Article>(
299+
var typedClient = await collection.AsTyped<Article>(
300300
cancellationToken: TestContext.Current.CancellationToken
301301
);
302302

@@ -337,7 +337,7 @@ public async Task TypedWorkflow_Query_BM25Search_ReturnsTypedResults()
337337
{
338338
// Arrange
339339
var collection = await CollectionFactory<Article>(_articleConfig);
340-
var typedClient = await collection.Use<Article>(
340+
var typedClient = await collection.AsTyped<Article>(
341341
cancellationToken: TestContext.Current.CancellationToken
342342
);
343343

@@ -388,7 +388,7 @@ public async Task TypedWorkflow_Update_ReplaceObject_Succeeds()
388388
{
389389
// Arrange
390390
var collection = await CollectionFactory<Article>(_articleConfig);
391-
var typedClient = await collection.Use<Article>(
391+
var typedClient = await collection.AsTyped<Article>(
392392
cancellationToken: TestContext.Current.CancellationToken
393393
);
394394

@@ -435,7 +435,7 @@ public async Task TypedWorkflow_Delete_ById_Succeeds()
435435
{
436436
// Arrange
437437
var collection = await CollectionFactory<Article>(_articleConfig);
438-
var typedClient = await collection.Use<Article>(
438+
var typedClient = await collection.AsTyped<Article>(
439439
cancellationToken: TestContext.Current.CancellationToken
440440
);
441441

@@ -468,7 +468,7 @@ public async Task TypedWorkflow_Count_ReturnsObjectCount()
468468
{
469469
// Arrange
470470
var collection = await CollectionFactory<Article>(_articleConfig);
471-
var typedClient = await collection.Use<Article>(
471+
var typedClient = await collection.AsTyped<Article>(
472472
cancellationToken: TestContext.Current.CancellationToken
473473
);
474474

@@ -514,7 +514,7 @@ public async Task TypedWorkflow_Iterator_IteratesAllObjects()
514514
{
515515
// Arrange
516516
var collection = await CollectionFactory<Article>(_articleConfig);
517-
var typedClient = await collection.Use<Article>(
517+
var typedClient = await collection.AsTyped<Article>(
518518
cancellationToken: TestContext.Current.CancellationToken
519519
);
520520

@@ -585,7 +585,7 @@ await collection.Tenants.Add(
585585
TestContext.Current.CancellationToken
586586
);
587587

588-
var typedClient = await collection.Use<Article>(
588+
var typedClient = await collection.AsTyped<Article>(
589589
cancellationToken: TestContext.Current.CancellationToken
590590
);
591591

@@ -603,7 +603,7 @@ public async Task TypedWorkflow_WithConsistencyLevel_CreatesNewTypedClient()
603603
{
604604
// Arrange
605605
var collection = await CollectionFactory<Article>(_articleConfig);
606-
var typedClient = await collection.Use<Article>(
606+
var typedClient = await collection.AsTyped<Article>(
607607
cancellationToken: TestContext.Current.CancellationToken
608608
);
609609

@@ -621,7 +621,7 @@ public async Task TypedWorkflow_Untyped_ReturnsUnderlyingCollectionClient()
621621
{
622622
// Arrange
623623
var collection = await CollectionFactory<Article>(_articleConfig);
624-
var typedClient = await collection.Use<Article>(
624+
var typedClient = await collection.AsTyped<Article>(
625625
cancellationToken: TestContext.Current.CancellationToken
626626
);
627627

@@ -643,7 +643,7 @@ public async Task TypeSafety_CompileTimeTypeChecking_EnforcesCorrectTypes()
643643
{
644644
// Arrange
645645
var collection = await CollectionFactory<Article>(_articleConfig);
646-
var typedClient = await collection.Use<Article>(
646+
var typedClient = await collection.AsTyped<Article>(
647647
cancellationToken: TestContext.Current.CancellationToken
648648
);
649649

src/Weaviate.Client.Tests/Unit/TestCollectionClientExtensions.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public async Task AsTyped_WithValidCollectionClient_ReturnsTypedCollectionClient
2424
var collectionClient = new CollectionClient(client, "Articles");
2525

2626
// Act
27-
var typedClient = await collectionClient.Use<TestArticle>(
27+
var typedClient = await collectionClient.AsTyped<TestArticle>(
2828
cancellationToken: TestContext.Current.CancellationToken
2929
);
3030

@@ -42,7 +42,7 @@ public async Task AsTyped_WithNullCollectionClient_ThrowsArgumentNullException()
4242

4343
// Act & Assert
4444
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
45-
await collectionClient!.Use<TestArticle>(
45+
await collectionClient!.AsTyped<TestArticle>(
4646
cancellationToken: TestContext.Current.CancellationToken
4747
)
4848
);
@@ -56,7 +56,7 @@ public async Task AsTyped_WithoutValidation_DoesNotValidateType()
5656
var collectionClient = new CollectionClient(client, "Articles");
5757

5858
// Act
59-
var typedClient = await collectionClient.Use<TestArticle>(
59+
var typedClient = await collectionClient.AsTyped<TestArticle>(
6060
validateType: false,
6161
cancellationToken: TestContext.Current.CancellationToken
6262
);
@@ -75,7 +75,7 @@ public async Task AsTyped_PreservesTenantConfiguration()
7575
var collectionClient = new CollectionClient(client, "Articles").WithTenant("tenant1");
7676

7777
// Act
78-
var typedClient = await collectionClient.Use<TestArticle>(
78+
var typedClient = await collectionClient.AsTyped<TestArticle>(
7979
cancellationToken: TestContext.Current.CancellationToken
8080
);
8181

@@ -94,7 +94,7 @@ public async Task AsTyped_PreservesConsistencyLevelConfiguration()
9494
);
9595

9696
// Act
97-
var typedClient = await collectionClient.Use<TestArticle>(
97+
var typedClient = await collectionClient.AsTyped<TestArticle>(
9898
cancellationToken: TestContext.Current.CancellationToken
9999
);
100100

@@ -113,7 +113,7 @@ public async Task AsTyped_WithChainedConfiguration_PreservesAllSettings()
113113
.WithConsistencyLevel(ConsistencyLevels.All);
114114

115115
// Act
116-
var typedClient = await collectionClient.Use<TestArticle>(
116+
var typedClient = await collectionClient.AsTyped<TestArticle>(
117117
cancellationToken: TestContext.Current.CancellationToken
118118
);
119119

@@ -131,7 +131,7 @@ public async Task AsTyped_ReturnsClientWithTypedDataQueryGenerate()
131131
var collectionClient = new CollectionClient(client, "Articles");
132132

133133
// Act
134-
var typedClient = await collectionClient.Use<TestArticle>(
134+
var typedClient = await collectionClient.AsTyped<TestArticle>(
135135
cancellationToken: TestContext.Current.CancellationToken
136136
);
137137

@@ -152,10 +152,10 @@ public async Task AsTyped_CalledMultipleTimes_ReturnsIndependentInstances()
152152
var collectionClient = new CollectionClient(client, "Articles");
153153

154154
// Act
155-
var typedClient1 = await collectionClient.Use<TestArticle>(
155+
var typedClient1 = await collectionClient.AsTyped<TestArticle>(
156156
cancellationToken: TestContext.Current.CancellationToken
157157
);
158-
var typedClient2 = await collectionClient.Use<TestArticle>(
158+
var typedClient2 = await collectionClient.AsTyped<TestArticle>(
159159
cancellationToken: TestContext.Current.CancellationToken
160160
);
161161

@@ -173,10 +173,10 @@ public async Task AsTyped_WithDifferentTypes_ReturnsCorrectTypedClients()
173173
var productsClient = new CollectionClient(client, "Products");
174174

175175
// Act
176-
var typedArticles = await articlesClient.Use<TestArticle>(
176+
var typedArticles = await articlesClient.AsTyped<TestArticle>(
177177
cancellationToken: TestContext.Current.CancellationToken
178178
);
179-
var typedProducts = await productsClient.Use<TestProduct>(
179+
var typedProducts = await productsClient.AsTyped<TestProduct>(
180180
cancellationToken: TestContext.Current.CancellationToken
181181
);
182182

@@ -195,7 +195,7 @@ public async Task AsTyped_RoundTrip_UntypedPropertyReturnsOriginalClient()
195195
var collectionClient = new CollectionClient(client, "Articles");
196196

197197
// Act
198-
var typedClient = await collectionClient.Use<TestArticle>(
198+
var typedClient = await collectionClient.AsTyped<TestArticle>(
199199
cancellationToken: TestContext.Current.CancellationToken
200200
);
201201
var untypedClient = typedClient.Untyped;

src/Weaviate.Client.Tests/Unit/TestTypedCollectionClient.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public void Tenant_ReturnsNullByDefault()
151151
var tenant = typedClient.Tenant;
152152

153153
// Assert
154-
Assert.Null(tenant);
154+
Assert.True(string.IsNullOrEmpty(tenant));
155155
}
156156

157157
[Fact]
@@ -308,8 +308,6 @@ public void MultipleInstances_HaveIndependentTypedClients()
308308

309309
private static WeaviateClient CreateWeaviateClient()
310310
{
311-
// Create a minimal WeaviateClient for testing wrapper logic
312-
// These tests don't make actual HTTP calls
313-
return new WeaviateClient(new ClientConfiguration(), null, null);
311+
return Mocks.MockWeaviateClient.CreateWithMockHandler().Client;
314312
}
315313
}

src/Weaviate.Client.Tests/Unit/TestTypedDataClient.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Weaviate.Client.Models;
2+
using Weaviate.Client.Tests.Unit.Mocks;
23
using Weaviate.Client.Typed;
34

45
namespace Weaviate.Client.Tests.Unit;
@@ -173,6 +174,8 @@ public void TypedDataClient_WrapsDataClient_MaintainsTypeConstraints()
173174

174175
private static WeaviateClient CreateWeaviateClient()
175176
{
176-
return new WeaviateClient(new ClientConfiguration(), null, null);
177+
return MockWeaviateClient
178+
.CreateWithMockHandler(handlerChainFactory: null, autoMeta: true)
179+
.Client;
177180
}
178181
}

src/Weaviate.Client.Tests/Unit/TestTypedQueryClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,6 @@ public void TypedQueryClient_WrapsQueryClient_MaintainsTypeConstraints()
382382

383383
private static WeaviateClient CreateWeaviateClient()
384384
{
385-
return new WeaviateClient(new ClientConfiguration(), null, null);
385+
return Mocks.MockWeaviateClient.CreateWithMockHandler().Client;
386386
}
387387
}

src/Weaviate.Client/CollectionsClient.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ public async Task<CollectionClient> Create(
2424
return new CollectionClient(_client, response.ToModel());
2525
}
2626

27+
public async Task<Typed.TypedCollectionClient<T>> Create<T>(
28+
Models.CollectionConfig collection,
29+
bool validateType = false,
30+
CancellationToken cancellationToken = default
31+
)
32+
where T : class, new()
33+
{
34+
var response = await _client.RestClient.CollectionCreate(
35+
collection.ToDto(),
36+
cancellationToken
37+
);
38+
39+
var innerClient = new CollectionClient(_client, response.ToModel());
40+
41+
return await innerClient.AsTyped<T>(validateType, cancellationToken);
42+
}
43+
2744
public async Task Delete(string collectionName, CancellationToken cancellationToken = default)
2845
{
2946
ArgumentException.ThrowIfNullOrEmpty(collectionName);
@@ -80,4 +97,21 @@ public CollectionClient Use(string name)
8097
{
8198
return new CollectionClient(_client, name);
8299
}
100+
101+
/// <summary>
102+
/// Creates a strongly-typed collection client for accessing a specific collection.
103+
/// The collection client provides type-safe data and query operations.
104+
/// </summary>
105+
/// <typeparam name="T">The C# type representing objects in this collection.</typeparam>
106+
/// <param name="name">The name of the collection.</param>
107+
/// <param name="validateType">If true, validates that type T is compatible with the collection schema on construction. Default is false for performance.</param>
108+
/// <returns>A TypedCollectionClient that provides strongly-typed operations.</returns>
109+
/// <exception cref="InvalidOperationException">Thrown if validateType is true and validation fails with errors.</exception>
110+
public async Task<Typed.TypedCollectionClient<T>> Use<T>(string name, bool validateType = false)
111+
where T : class, new()
112+
{
113+
var innerClient = Use(name);
114+
115+
return await innerClient.AsTyped<T>(validateType);
116+
}
83117
}

0 commit comments

Comments
 (0)