Skip to content

Commit

Permalink
remove redundant error enum
Browse files Browse the repository at this point in the history
  • Loading branch information
rex committed Oct 25, 2024
1 parent 829ce80 commit 4b705ed
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 93 deletions.
42 changes: 42 additions & 0 deletions mobile-client/ToDoList/Model/ToDoError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

/// An enumeration representing errors that can occur in the ToDo application.
enum ToDoError: Error, LocalizedError, Equatable {
case networkError(Error)
case decodingError(Error)
case invalidResponse(Int)
case localError
case unknownError

var errorDescription: String? {
switch self {
case let .networkError(error):
"Network error: \(error.localizedDescription)"
case let .decodingError(error):
"Decoding error: \(error.localizedDescription)"
case let .invalidResponse(statusCode):
"Invalid response from server: \(statusCode)"
case .localError:
"Unknown local error while fetching local storage data"
case .unknownError:
"Unknown error"
}
}

static func == (lhs: ToDoError, rhs: ToDoError) -> Bool {
switch (lhs, rhs) {
case let (.networkError(error1), .networkError(error2)),
let (.decodingError(error1), .decodingError(error2)):
(error1 as NSError).code == (error2 as NSError).code &&
(error1.localizedDescription == error2.localizedDescription)
case let (.invalidResponse(statusCode1), .invalidResponse(statusCode2)):
statusCode1 == statusCode2
case (.localError, .localError):
true
case (.unknownError, .unknownError):
true
default:
false
}
}
}
58 changes: 17 additions & 41 deletions mobile-client/ToDoList/Model/ToDoRemoteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,6 @@ protocol ToDoRemoteServiceProtocol {
func deleteToDo(id: Int) async throws
}

enum ToDoServiceError: Error, LocalizedError, Equatable {
case networkError(Error)
case decodingError(Error)
case invalidResponse(Int)

var errorDescription: String? {
switch self {
case let .networkError(error):
"Network error: \(error.localizedDescription)"
case let .decodingError(error):
"Decoding error: \(error.localizedDescription)"
case let .invalidResponse(statusCode):
"Invalid response from server: \(statusCode)"
}
}

static func == (lhs: ToDoServiceError, rhs: ToDoServiceError) -> Bool {
switch (lhs, rhs) {
case let (.networkError(lhsError), .networkError(rhsError)):
lhsError.localizedDescription == rhsError.localizedDescription
case let (.decodingError(lhsError), .decodingError(rhsError)):
lhsError.localizedDescription == rhsError.localizedDescription
case let (.invalidResponse(lhsStatusCode), .invalidResponse(rhsStatusCode)):
lhsStatusCode == rhsStatusCode
default:
false
}
}
}

final class ToDoRemoteService: ToDoRemoteServiceProtocol {
private let endPoint = "http://localhost:5000/todos"
private let localService: ToDoLocalServiceProtocol
Expand All @@ -50,13 +20,19 @@ final class ToDoRemoteService: ToDoRemoteServiceProtocol {
self.dataFetcher = dataFetcher
}

/// Handles errors and converts them into ToDoError types.
private func handleError(_ error: Error) throws -> Never {
if let urlError = error as? URLError {
throw ToDoServiceError.networkError(urlError)
} else if let decodingError = error as? DecodingError {
throw ToDoServiceError.decodingError(decodingError)
} else {
throw error
if let toDoError = error as? ToDoError {
throw toDoError
}

switch error {
case let urlError as URLError:
throw ToDoError.networkError(urlError)
case let decodingError as DecodingError:
throw ToDoError.decodingError(decodingError)
default:
throw ToDoError.unknownError
}
}

Expand All @@ -66,10 +42,10 @@ final class ToDoRemoteService: ToDoRemoteServiceProtocol {
let (data, response)
= try await dataFetcher.dataRequest(from: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw ToDoServiceError.invalidResponse(0)
throw ToDoError.invalidResponse(0)
}
guard (200 ... 299).contains(httpResponse.statusCode) else {
throw ToDoServiceError.invalidResponse(httpResponse.statusCode)
throw ToDoError.invalidResponse(httpResponse.statusCode)
}
let decodedResponse = try JSONDecoder().decode(FetchToDoResponse.self, from: data)
var remoteTodos = decodedResponse.data
Expand Down Expand Up @@ -116,11 +92,11 @@ final class ToDoRemoteService: ToDoRemoteServiceProtocol {
let request = RequestBuilder.build(url: URL(string: endPoint)!, method: "POST", headers: ["Content-Type": "application/json"], body: encodedData)
let (data, response) = try await dataFetcher.dataRequest(from: request)
guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
throw ToDoServiceError.invalidResponse((response as? HTTPURLResponse)?.statusCode ?? 0)
throw ToDoError.invalidResponse((response as? HTTPURLResponse)?.statusCode ?? 0)
}
let decodedResponse = try JSONDecoder().decode(AddToDoResponse.self, from: data)
guard decodedResponse.success else {
throw ToDoServiceError.invalidResponse(httpResponse.statusCode)
throw ToDoError.invalidResponse(httpResponse.statusCode)
}

// Save the new ToDo item in local storage
Expand All @@ -139,7 +115,7 @@ final class ToDoRemoteService: ToDoRemoteServiceProtocol {
let request = RequestBuilder.build(url: URL(string: deleteURL)!, method: "DELETE", headers: nil, body: nil)
let (_, response) = try await dataFetcher.dataRequest(from: request)
guard let httpResponse = response as? HTTPURLResponse, (200 ... 299).contains(httpResponse.statusCode) else {
throw ToDoServiceError.invalidResponse((response as? HTTPURLResponse)?.statusCode ?? 0)
throw ToDoError.invalidResponse((response as? HTTPURLResponse)?.statusCode ?? 0)
}

// Delete the local ToDo item
Expand Down
2 changes: 1 addition & 1 deletion mobile-client/ToDoList/Reducer/AddToDoReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct AddToDoReducer {
let newTodo = try await toDoService.postToDo(state.todo)
await send(.saveResponse(.success(newTodo)))
} catch {
await send(.saveResponse(.failure(.networkError(error))))
await send(.saveResponse(.failure(error as? ToDoError ?? .unknownError)))
}
}

Expand Down
38 changes: 0 additions & 38 deletions mobile-client/ToDoList/Reducer/ToDoListReducer.swift
Original file line number Diff line number Diff line change
@@ -1,43 +1,5 @@
import ComposableArchitecture
import Foundation
import UIKit

enum ToDoError: Error, LocalizedError, Equatable {
case networkError(Error)
case decodingError(Error)
case invalidResponse(Int)
case localError

var errorDescription: String? {
switch self {
case let .networkError(error):
"Network error: \(error.localizedDescription)"
case let .decodingError(error):
"Decoding error: \(error.localizedDescription)"
case let .invalidResponse(statusCode):
"Invalid response from server: \(statusCode)"
case .localError:
"Unknow local error while fetching local storage data"
}
}

static func == (lhs: ToDoError, rhs: ToDoError) -> Bool {
switch (lhs, rhs) {
case let (.networkError(error1), .networkError(error2)):
(error1 as NSError).code == (error2 as NSError).code &&
(error1.localizedDescription == error2.localizedDescription)
case let (.decodingError(error1), .decodingError(error2)):
(error1 as NSError).code == (error2 as NSError).code &&
(error1.localizedDescription == error2.localizedDescription)
case let (.invalidResponse(statusCode1), .invalidResponse(statusCode2)):
statusCode1 == statusCode2
case (.localError, .localError):
true
default:
false
}
}
}

@Reducer
struct ToDoListReducer {
Expand Down
22 changes: 9 additions & 13 deletions mobile-client/ToDoListTests/ToDoRemoveServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ struct ToDoRemoteServiceTests {
Issue.record("Unexpected Error")
} catch {
// Handle the error thrown by the fetch method.
if let serviceError = error as? ToDoServiceError {
if let serviceError = error as? ToDoError {
// Expect the error to be of type ToDoServiceError and verify the status code.
#expect(serviceError == ToDoServiceError.invalidResponse(500))
#expect(serviceError == ToDoError.invalidResponse(500))
} else {
// Record an unexpected error if the error type does not match.
Issue.record("Unexpected Error")
Expand Down Expand Up @@ -226,29 +226,23 @@ struct ToDoRemoteServiceTests {
createdAt: "2024-10-10T10:00:00.333Z",
updatedAt: "2024-10-15T10:00:00.333Z")

// Prepare the response to simulate a successful post.
let fetchResponse = AddToDoResponse(success: true, data: todo)

// Encode the response into JSON data.
let jsonData = try! JSONEncoder().encode(fetchResponse)

// Define the URL for the post request.
let url = URL(string: "http://localhost:5000/todos")!

// Create an HTTP response with a 500 status code to simulate a server error.
let httpResponse = HTTPURLResponse(url: url, statusCode: 500, httpVersion: nil, headerFields: nil)!

// Set the result of the mock data fetcher to simulate a server error.
mockDataFetcher.result = (jsonData, httpResponse)
mockDataFetcher.result = (Data(), httpResponse)

do {
// Attempt to post the ToDo item.
_ = try await service.postToDo(todo)
Issue.record("Unexpected Error")
} catch {
// Verify that the error returned is of the expected type.
if let serviceError = error as? ToDoServiceError {
#expect(serviceError == ToDoServiceError.invalidResponse(500))
if let serviceError = error as? ToDoError {
#expect(serviceError == ToDoError.invalidResponse(500))
} else {
Issue.record("Unexpected Error")
}
Expand Down Expand Up @@ -286,6 +280,7 @@ struct ToDoRemoteServiceTests {

// Verify the title of the posted ToDo matches the original.
#expect(remoteToDo.title == todo.title)

// Expect the local service's todos count to be 1 after the post.
#expect(mockLocalService.todos.count == 1)

Expand Down Expand Up @@ -333,6 +328,7 @@ struct ToDoRemoteServiceTests {

// Verify the title of the posted ToDo matches the original.
#expect(remoteToDo.title == todo.title)

// Expect the local service's todos count to be 1 after the post.
#expect(mockLocalService.todos.count == 1)

Expand All @@ -346,8 +342,8 @@ struct ToDoRemoteServiceTests {
Issue.record("Unexpected Error")
} catch {
// Verify that the error returned is of the expected type.
if let serviceError = error as? ToDoServiceError {
#expect(serviceError == ToDoServiceError.invalidResponse(500))
if let serviceError = error as? ToDoError {
#expect(serviceError == ToDoError.invalidResponse(500))
} else {
Issue.record("Unexpected Error")
}
Expand Down

0 comments on commit 4b705ed

Please sign in to comment.