From 913fb0837aba8587f634958f09cc1b567297f350 Mon Sep 17 00:00:00 2001 From: Kevin Hermawan <84965338+kevinhermawan@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:05:30 +0700 Subject: [PATCH] refactor: adds `statusCode` to `serverError` (#10) --- README.md | 4 ++-- .../Documentation.docc/Documentation.md | 4 ++-- .../LLMChatAnthropic/LLMChatAnthropic.swift | 14 +++++++---- .../LLMChatAnthropicError.swift | 6 +++-- .../ChatCompletionTests.swift | 24 +++++++++++-------- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ba55100..c3fe5c8 100644 --- a/README.md +++ b/README.md @@ -209,9 +209,9 @@ do { print(completion.content.first?.text ?? "No response") } catch let error as LLMChatAnthropicError { switch error { - case .serverError(let message): + case .serverError(let statusCode, let message): // Handle server-side errors (e.g., invalid API key, rate limits) - print("Server Error: \(message)") + print("Server Error [\(statusCode)]: \(message)") case .networkError(let error): // Handle network-related errors (e.g., no internet connection) print("Network Error: \(error.localizedDescription)") diff --git a/Sources/LLMChatAnthropic/Documentation.docc/Documentation.md b/Sources/LLMChatAnthropic/Documentation.docc/Documentation.md index 247d1ce..602e126 100644 --- a/Sources/LLMChatAnthropic/Documentation.docc/Documentation.md +++ b/Sources/LLMChatAnthropic/Documentation.docc/Documentation.md @@ -180,9 +180,9 @@ do { print(completion.content.first?.text ?? "No response") } catch let error as LLMChatAnthropicError { switch error { - case .serverError(let message): + case .serverError(let statusCode, let message): // Handle server-side errors (e.g., invalid API key, rate limits) - print("Server Error: \(message)") + print("Server Error [\(statusCode)]: \(message)") case .networkError(let error): // Handle network-related errors (e.g., no internet connection) print("Network Error: \(error.localizedDescription)") diff --git a/Sources/LLMChatAnthropic/LLMChatAnthropic.swift b/Sources/LLMChatAnthropic/LLMChatAnthropic.swift index f7b0b8b..9e39a97 100644 --- a/Sources/LLMChatAnthropic/LLMChatAnthropic.swift +++ b/Sources/LLMChatAnthropic/LLMChatAnthropic.swift @@ -90,16 +90,16 @@ private extension LLMChatAnthropic { let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { - throw LLMChatAnthropicError.serverError(response.description) + throw LLMChatAnthropicError.serverError(statusCode: 0, message: response.description) } // Check for API errors first, as they might come with 200 status if let errorResponse = try? JSONDecoder().decode(ChatCompletionError.self, from: data) { - throw LLMChatAnthropicError.serverError(errorResponse.error.message) + throw LLMChatAnthropicError.serverError(statusCode: httpResponse.statusCode, message: errorResponse.error.message) } guard 200...299 ~= httpResponse.statusCode else { - throw LLMChatAnthropicError.serverError(response.description) + throw LLMChatAnthropicError.serverError(statusCode: httpResponse.statusCode, message: response.description) } return try JSONDecoder().decode(ChatCompletion.self, from: data) @@ -124,8 +124,12 @@ private extension LLMChatAnthropic { let request = try createRequest(for: endpoint, with: body) let (bytes, response) = try await URLSession.shared.bytes(for: request) - guard let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else { - throw LLMChatAnthropicError.serverError(response.description) + guard let httpResponse = response as? HTTPURLResponse else { + throw LLMChatAnthropicError.serverError(statusCode: 0, message: response.description) + } + + guard 200...299 ~= httpResponse.statusCode else { + throw LLMChatAnthropicError.serverError(statusCode: httpResponse.statusCode, message: response.description) } var currentChunk = ChatCompletionChunk(id: "", model: "", role: "") diff --git a/Sources/LLMChatAnthropic/LLMChatAnthropicError.swift b/Sources/LLMChatAnthropic/LLMChatAnthropicError.swift index b8d19b0..46fc1b7 100644 --- a/Sources/LLMChatAnthropic/LLMChatAnthropicError.swift +++ b/Sources/LLMChatAnthropic/LLMChatAnthropicError.swift @@ -21,8 +21,10 @@ public enum LLMChatAnthropicError: Error, Sendable { /// An error returned by the server. /// - /// - Parameter message: The error message received from the server. - case serverError(String) + /// - Parameters: + /// - statusCode: The HTTP status code returned by the server. + /// - message: The error message received from the server. + case serverError(statusCode: Int, message: String) /// An error that occurs during stream processing. case streamError diff --git a/Tests/LLMChatAnthropicTests/ChatCompletionTests.swift b/Tests/LLMChatAnthropicTests/ChatCompletionTests.swift index d4900f8..64f7902 100644 --- a/Tests/LLMChatAnthropicTests/ChatCompletionTests.swift +++ b/Tests/LLMChatAnthropicTests/ChatCompletionTests.swift @@ -138,14 +138,15 @@ final class ChatCompletionTests: XCTestCase { extension ChatCompletionTests { func testServerError() async throws { let mockErrorResponse = """ - { - "error": { - "message": "Invalid API key provided" + { + "error": { + "message": "Invalid API key provided" + } } - } - """ + """ URLProtocolMock.mockData = mockErrorResponse.data(using: .utf8) + URLProtocolMock.mockStatusCode = 401 do { _ = try await chat.send(model: "claude-3-5-sonnet", messages: messages) @@ -153,7 +154,8 @@ extension ChatCompletionTests { XCTFail("Expected serverError to be thrown") } catch let error as LLMChatAnthropicError { switch error { - case .serverError(let message): + case .serverError(let statusCode, let message): + XCTAssertEqual(statusCode, 401) XCTAssertEqual(message, "Invalid API key provided") default: XCTFail("Expected serverError but got \(error)") @@ -183,8 +185,8 @@ extension ChatCompletionTests { } func testHTTPError() async throws { - URLProtocolMock.mockStatusCode = 429 URLProtocolMock.mockData = "Rate limit exceeded".data(using: .utf8) + URLProtocolMock.mockStatusCode = 429 do { _ = try await chat.send(model: "claude-3-5-sonnet", messages: messages) @@ -192,7 +194,8 @@ extension ChatCompletionTests { XCTFail("Expected serverError to be thrown") } catch let error as LLMChatAnthropicError { switch error { - case .serverError(let message): + case .serverError(let statusCode, let message): + XCTAssertEqual(statusCode, 429) XCTAssertTrue(message.contains("429")) default: XCTFail("Expected serverError but got \(error)") @@ -283,8 +286,8 @@ extension ChatCompletionTests { } func testStreamHTTPError() async throws { - URLProtocolMock.mockStatusCode = 503 URLProtocolMock.mockStreamData = [""] + URLProtocolMock.mockStatusCode = 503 do { for try await _ in chat.stream(model: "claude-3-5-sonnet", messages: messages) { @@ -292,7 +295,8 @@ extension ChatCompletionTests { } } catch let error as LLMChatAnthropicError { switch error { - case .serverError(let message): + case .serverError(let statusCode, let message): + XCTAssertEqual(statusCode, 503) XCTAssertTrue(message.contains("503")) default: XCTFail("Expected serverError but got \(error)")