Skip to content

Commit ceefe27

Browse files
committed
feat: Enhance exception handling for backup conflicts in ExceptionHelper and add corresponding unit tests
1 parent e0d1ca7 commit ceefe27

File tree

3 files changed

+125
-62
lines changed

3 files changed

+125
-62
lines changed

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,4 +315,70 @@ public void MapHttpException_UnprocessableEntity_ReturnsUnprocessableEntityExcep
315315
// Assert
316316
Assert.IsType<WeaviateUnprocessableEntityException>(result);
317317
}
318+
319+
[Fact]
320+
public void MapHttpException_UnprocessableEntityWithBackupConflictMessage_ReturnsBackupConflictException()
321+
{
322+
// Arrange
323+
var innerEx = new Weaviate.Client.Rest.WeaviateUnexpectedStatusCodeException(
324+
HttpStatusCode.UnprocessableEntity,
325+
new HashSet<HttpStatusCode> { HttpStatusCode.OK },
326+
"backup concurrent-backup-123 already in progress"
327+
);
328+
329+
// Act
330+
var result = ExceptionHelper.MapHttpException(
331+
HttpStatusCode.UnprocessableEntity,
332+
"backup concurrent-backup-123 already in progress",
333+
innerEx
334+
);
335+
336+
// Assert
337+
var backupConflictEx = Assert.IsType<WeaviateBackupConflictException>(result);
338+
Assert.Contains("already in progress", backupConflictEx.Message);
339+
}
340+
341+
[Fact]
342+
public void MapHttpException_UnprocessableEntityWithRestoreConflictMessage_ReturnsBackupConflictException()
343+
{
344+
// Arrange
345+
var innerEx = new Weaviate.Client.Rest.WeaviateUnexpectedStatusCodeException(
346+
HttpStatusCode.UnprocessableEntity,
347+
new HashSet<HttpStatusCode> { HttpStatusCode.OK },
348+
"restoration restore-456 already in progress"
349+
);
350+
351+
// Act
352+
var result = ExceptionHelper.MapHttpException(
353+
HttpStatusCode.UnprocessableEntity,
354+
"restoration restore-456 already in progress",
355+
innerEx
356+
);
357+
358+
// Assert
359+
var backupConflictEx = Assert.IsType<WeaviateBackupConflictException>(result);
360+
Assert.Contains("already in progress", backupConflictEx.Message);
361+
}
362+
363+
[Fact]
364+
public void MapHttpException_InternalServerErrorWithConflictMessage_DoesNotReturnBackupConflictException()
365+
{
366+
// Arrange - 500 status code should NOT trigger backup conflict, only 422
367+
var innerEx = new Weaviate.Client.Rest.WeaviateUnexpectedStatusCodeException(
368+
HttpStatusCode.InternalServerError,
369+
new HashSet<HttpStatusCode> { HttpStatusCode.OK },
370+
"backup already in progress"
371+
);
372+
373+
// Act & Assert
374+
// Should throw the original exception since it's not recognized
375+
var ex = Assert.Throws<Weaviate.Client.Rest.WeaviateUnexpectedStatusCodeException>(() =>
376+
ExceptionHelper.MapHttpException(
377+
HttpStatusCode.InternalServerError,
378+
"backup already in progress",
379+
innerEx
380+
)
381+
);
382+
Assert.Same(innerEx, ex);
383+
}
318384
}

src/Weaviate.Client/ExceptionHelper.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static WeaviateException MapHttpException(
6262
|| statusCode == HttpStatusCode.InternalServerError
6363
)
6464
{
65-
var messageBasedException = MapErrorMessage(errorMessage, innerException);
65+
var messageBasedException = MapErrorMessage(errorMessage, innerException, statusCode);
6666
if (messageBasedException != null)
6767
{
6868
return messageBasedException;
@@ -142,13 +142,30 @@ string defaultMessage
142142
/// Maps error messages to specific exceptions regardless of protocol.
143143
/// Returns null if no specific exception type matches.
144144
/// </summary>
145-
private static WeaviateException? MapErrorMessage(string errorMessage, Exception innerException)
145+
/// <param name="errorMessage">The error message to analyze</param>
146+
/// <param name="innerException">The original exception</param>
147+
/// <param name="statusCode">Optional HTTP status code for more specific matching</param>
148+
private static WeaviateException? MapErrorMessage(
149+
string errorMessage,
150+
Exception innerException,
151+
HttpStatusCode? statusCode = null
152+
)
146153
{
147154
if (string.IsNullOrEmpty(errorMessage))
148155
{
149156
return null;
150157
}
151158

159+
// Check for backup/restore conflict error (422 only)
160+
if (
161+
statusCode.HasValue
162+
&& statusCode.Value == HttpStatusCode.UnprocessableEntity
163+
&& errorMessage.Contains("already in progress", StringComparison.OrdinalIgnoreCase)
164+
)
165+
{
166+
return new WeaviateBackupConflictException(innerException);
167+
}
168+
152169
// Check for collection limit error
153170
if (
154171
errorMessage.Contains(

src/Weaviate.Client/Rest/Backup.cs

Lines changed: 40 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,26 @@ internal partial class WeaviateRestClient
1212
CancellationToken cancellationToken = default
1313
)
1414
{
15-
try
16-
{
17-
var response = await _httpClient.PostAsJsonAsync(
18-
WeaviateEndpoints.Backups(backend.ToEnumMemberString()!),
19-
request,
20-
options: RestJsonSerializerOptions,
21-
cancellationToken: cancellationToken
22-
);
23-
await response.ManageStatusCode(
24-
[
25-
HttpStatusCode.OK,
26-
// HttpStatusCode.BadRequest,
27-
// HttpStatusCode.Unauthorized,
28-
// HttpStatusCode.Forbidden,
29-
// HttpStatusCode.NotFound,
30-
// HttpStatusCode.Conflict,
31-
// HttpStatusCode.InternalServerError,
32-
],
33-
"create backup",
34-
ResourceType.Backup
35-
);
36-
return await response.DecodeAsync<Dto.BackupCreateResponse>(cancellationToken);
37-
}
38-
catch (WeaviateUnexpectedStatusCodeException ex)
39-
when (ex.StatusCode == HttpStatusCode.UnprocessableEntity
40-
&& ex.Message.Contains("already in progress")
41-
)
42-
{
43-
throw new WeaviateBackupConflictException(ex);
44-
}
15+
var response = await _httpClient.PostAsJsonAsync(
16+
WeaviateEndpoints.Backups(backend.ToEnumMemberString()!),
17+
request,
18+
options: RestJsonSerializerOptions,
19+
cancellationToken: cancellationToken
20+
);
21+
await response.ManageStatusCode(
22+
[
23+
HttpStatusCode.OK,
24+
// HttpStatusCode.BadRequest,
25+
// HttpStatusCode.Unauthorized,
26+
// HttpStatusCode.Forbidden,
27+
// HttpStatusCode.NotFound,
28+
// HttpStatusCode.Conflict,
29+
// HttpStatusCode.InternalServerError,
30+
],
31+
"create backup",
32+
ResourceType.Backup
33+
);
34+
return await response.DecodeAsync<Dto.BackupCreateResponse>(cancellationToken);
4535
}
4636

4737
internal async Task<List<Dto.Anonymous3>> BackupList(
@@ -132,36 +122,26 @@ await response.ManageStatusCode(
132122
CancellationToken cancellationToken = default
133123
)
134124
{
135-
try
136-
{
137-
var response = await _httpClient.PostAsJsonAsync(
138-
WeaviateEndpoints.BackupRestore(backend.ToEnumMemberString()!, id),
139-
request,
140-
options: RestJsonSerializerOptions,
141-
cancellationToken: cancellationToken
142-
);
143-
await response.ManageStatusCode(
144-
[
145-
HttpStatusCode.OK,
146-
// HttpStatusCode.BadRequest,
147-
// HttpStatusCode.Unauthorized,
148-
// HttpStatusCode.Forbidden,
149-
// HttpStatusCode.NotFound,
150-
// HttpStatusCode.Conflict,
151-
// HttpStatusCode.InternalServerError,
152-
],
153-
"backup restore",
154-
ResourceType.Backup
155-
);
156-
return await response.DecodeAsync<Dto.BackupRestoreResponse>(cancellationToken);
157-
}
158-
catch (WeaviateUnexpectedStatusCodeException ex)
159-
when (ex.StatusCode == HttpStatusCode.UnprocessableEntity
160-
&& ex.Message.Contains("already in progress")
161-
)
162-
{
163-
throw new WeaviateBackupConflictException(ex);
164-
}
125+
var response = await _httpClient.PostAsJsonAsync(
126+
WeaviateEndpoints.BackupRestore(backend.ToEnumMemberString()!, id),
127+
request,
128+
options: RestJsonSerializerOptions,
129+
cancellationToken: cancellationToken
130+
);
131+
await response.ManageStatusCode(
132+
[
133+
HttpStatusCode.OK,
134+
// HttpStatusCode.BadRequest,
135+
// HttpStatusCode.Unauthorized,
136+
// HttpStatusCode.Forbidden,
137+
// HttpStatusCode.NotFound,
138+
// HttpStatusCode.Conflict,
139+
// HttpStatusCode.InternalServerError,
140+
],
141+
"backup restore",
142+
ResourceType.Backup
143+
);
144+
return await response.DecodeAsync<Dto.BackupRestoreResponse>(cancellationToken);
165145
}
166146

167147
internal async Task<Dto.BackupRestoreStatusResponse> BackupRestoreStatus(

0 commit comments

Comments
 (0)