Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Modules/Sources/Fakes/NetworkingCore.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension NetworkingCore.DotcomError {
/// Returns a "ready to use" type filled with fake values.
///
public static func fake() -> NetworkingCore.DotcomError {
.empty
.empty()
}
}
extension NetworkingCore.MetaContainer {
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Networking/Remote/AccountRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public enum CreateAccountError: Error, Equatable {
/// Decodable Initializer.
///
init(dotcomError error: DotcomError) {
if case let .unknown(code, message) = error {
if case let .unknown(code, message, _) = error {
switch code {
case Constants.emailExists:
self = .emailExists
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Networking/Remote/DevicesRemote.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class DevicesRemote: Remote {

enqueue(request, mapper: mapper) { (success, error) in
guard success == true else {
completion(error ?? DotcomError.empty)
completion(error ?? DotcomError.empty())
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public enum InstallThemeError: Error {

init?(_ error: Error) {
guard let dotcomError = error as? DotcomError,
case let .unknown(code, _) = dotcomError else {
case let .unknown(code, _, _) = dotcomError else {
return nil
}

Expand Down
52 changes: 28 additions & 24 deletions Modules/Sources/NetworkingCore/Model/DotcomError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,79 @@ public enum DotcomError: Error, Decodable, Equatable, GeneratedFakeable {

/// Non explicit reason
///
case empty
case empty(data: [String: AnyDecodable]? = nil)

/// Missing Token!
///
case unauthorized
case unauthorized(data: [String: AnyDecodable]? = nil)

/// We're not properly authenticated
///
case invalidToken
case invalidToken(data: [String: AnyDecodable]? = nil)

/// Remote Request Failed
///
case requestFailed
case requestFailed(data: [String: AnyDecodable]? = nil)

/// No route was found matching the URL and request method
///
case noRestRoute
case noRestRoute(data: [String: AnyDecodable]? = nil)

/// Jetpack is not connected
///
/// This can be caused by an `unknown_token` error from Jetpack
/// or an `invalid_blog` error from WordPress.com Stats.
///
case jetpackNotConnected
case jetpackNotConnected(data: [String: AnyDecodable]? = nil)

/// Unknown: Represents an unmapped remote error. Capisce?
///
case unknown(code: String, message: String?)
case unknown(code: String, message: String?, data: [String: AnyDecodable]?)

/// Stats error cases - API documentation of possible errors:
/// https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/
/// Note: when the cases get large, consider refactoring them into a separate error enum that conforms to a Dotcom error protocol

/// No permission to view site stats
case noStatsPermission
case noStatsPermission(data: [String: AnyDecodable]? = nil)

/// Jetpack site stats module disabled
case statsModuleDisabled
case statsModuleDisabled(data: [String: AnyDecodable]? = nil)

/// The requested resourced does not exist remotely
case resourceDoesNotExist
case resourceDoesNotExist(data: [String: AnyDecodable]? = nil)

/// Decodable Initializer.
///
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let error = try container.decode(String.self, forKey: .error)
let message = try container.decodeIfPresent(String.self, forKey: .message)
let data = try container.decodeIfPresent([String: AnyDecodable].self, forKey: .data)

switch error {
case Constants.invalidToken:
self = .invalidToken
self = .invalidToken(data: data)
case Constants.requestFailed:
self = .requestFailed
self = .requestFailed(data: data)
case Constants.unauthorized where message == ErrorMessages.noStatsPermission:
self = .noStatsPermission
self = .noStatsPermission(data: data)
case Constants.unauthorized:
self = .unauthorized
self = .unauthorized(data: data)
case Constants.noRestRoute:
self = .noRestRoute
self = .noRestRoute(data: data)
case Constants.invalidBlog where message == ErrorMessages.statsModuleDisabled:
self = .statsModuleDisabled
self = .statsModuleDisabled(data: data)
case Constants.restTermInvalid where message == ErrorMessages.resourceDoesNotExist:
self = .resourceDoesNotExist
self = .resourceDoesNotExist(data: data)
case Constants.unknownToken,
Constants.invalidBlog where message == ErrorMessages.jetpackNotConnected:
self = .jetpackNotConnected
self = .jetpackNotConnected(data: data)
default:
self = .unknown(code: error, message: message)
self = .unknown(code: error, message: message, data: data)
}
}


/// Constants for Possible Error Identifiers
///
private enum Constants {
Expand All @@ -97,6 +97,7 @@ public enum DotcomError: Error, Decodable, Equatable, GeneratedFakeable {
private enum CodingKeys: String, CodingKey {
case error
case message
case data
}

/// Possible Error Messages
Expand Down Expand Up @@ -134,7 +135,7 @@ extension DotcomError: CustomStringConvertible {
return NSLocalizedString("Dotcom Resource does not exist", comment: "WordPress.com error thrown when a requested resource does not exist remotely.")
case .jetpackNotConnected:
return NSLocalizedString("Jetpack Not Connected", comment: "WordPress.com error thrown when Jetpack is not connected.")
case .unknown(let code, let message):
case .unknown(let code, let message, _):
let theMessage = message ?? String()
let messageFormat = NSLocalizedString(
"Dotcom Error: [%1$@] %2$@",
Expand All @@ -150,14 +151,17 @@ extension DotcomError: CustomStringConvertible {
//
public func ==(lhs: DotcomError, rhs: DotcomError) -> Bool {
switch (lhs, rhs) {
case (.requestFailed, .requestFailed),
case (.empty, .empty),
(.unauthorized, .unauthorized),
(.invalidToken, .invalidToken),
(.requestFailed, .requestFailed),
(.noRestRoute, .noRestRoute),
(.jetpackNotConnected, .jetpackNotConnected),
(.noStatsPermission, .noStatsPermission),
(.statsModuleDisabled, .statsModuleDisabled),
(.jetpackNotConnected, .jetpackNotConnected):
(.resourceDoesNotExist, .resourceDoesNotExist):
return true
case let (.unknown(codeLHS, _), .unknown(codeRHS, _)):
case let (.unknown(codeLHS, _, _), .unknown(codeRHS, _, _)):
return codeLHS == codeRHS
default:
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public final class NotificationsRemote: Remote, NotificationsRemoteProtocol {

enqueue(request, mapper: mapper) { (success, error) in
guard success == true else {
completion(error ?? DotcomError.empty)
completion(error ?? DotcomError.empty())
return
}

Expand All @@ -99,7 +99,7 @@ public final class NotificationsRemote: Remote, NotificationsRemoteProtocol {

enqueue(request, mapper: mapper) { (success, error) in
guard success == true else {
completion(error ?? DotcomError.empty)
completion(error ?? DotcomError.empty())
return
}

Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Yosemite/Stores/CouponsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct CouponsError: Error, LocalizedError {

public init?(underlyingError error: Error) {
switch error {
case DotcomError.unknown(Constants.invalidCouponCode, let message):
case DotcomError.unknown(Constants.invalidCouponCode, let message, _):
self.message = message ?? Localizations.defaultCouponsError
self.underlyingError = error
case let NetworkError.unacceptableStatusCode(_, response):
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Yosemite/Stores/OrderStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ private extension OrderStore {
})
case .failure(let error):
if let dotcomError = error as? DotcomError,
case let .unknown(code, message) = dotcomError {
case let .unknown(code, message, _) = dotcomError {
switch code {
case "woocommerce_rest_gift_card_cannot_apply":
return onCompletion(.failure(GiftCardError.cannotApply(reason: message)))
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Yosemite/Stores/PaymentsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public enum PaymentsError: Error, LocalizedError {

private static func unwrapError(error: Error) -> PaymentsErrorConvertible? {
switch error {
case let DotcomError.unknown(code, message):
case let DotcomError.unknown(code, message, _):
return PaymentsDotcomErrorDetails(code: code, message: message)
case let NetworkError.unacceptableStatusCode(_, response):
guard let response,
Expand Down
11 changes: 6 additions & 5 deletions Modules/Sources/Yosemite/Stores/ProductStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,9 @@ private extension ProductStore {
shouldDeleteExistingProducts: shouldDeleteExistingProducts)
let hasNextPage = products.count == pageSize
return hasNextPage
} catch let error as DotcomError where error == .unknown(code: "rest_invalid_param", message: "Invalid parameter(s): type") {
if let productType,
} catch let error as DotcomError {
if case .unknown(code: "rest_invalid_param", message: "Invalid parameter(s): type", data: _) = error,
let productType,
ProductType.coreTypes.contains(productType) == false {
return false
}
Expand Down Expand Up @@ -684,7 +685,7 @@ private extension ProductStore {
feature: .productDetailsFromScannedTexts,
responseFormat: .json)
guard let jsonData = jsonString.data(using: .utf8) else {
return completion(.failure(DotcomError.resourceDoesNotExist))
return completion(.failure(DotcomError.resourceDoesNotExist()))
}
let details = try JSONDecoder().decode(ProductDetailsFromScannedTexts.self, from: jsonData)
completion(.success(.init(name: details.name, description: details.description)))
Expand Down Expand Up @@ -1388,7 +1389,7 @@ public enum ProductUpdateError: Error, Equatable {
return
}
switch dotcomError {
case let .unknown(code, message):
case let .unknown(code, message, _):
guard let errorCode = ErrorCode(rawValue: code) else {
self = .unknown(error: dotcomError.toAnyError)
return
Expand Down Expand Up @@ -1476,7 +1477,7 @@ public enum ProductLoadError: Error, Equatable {
case unknown(error: AnyError)

init(underlyingError error: Error) {
guard case let DotcomError.unknown(code, _) = error else {
guard case let DotcomError.unknown(code, _, _) = error else {
self = .unknown(error: error.toAnyError)
return
}
Expand Down
4 changes: 2 additions & 2 deletions Modules/Sources/Yosemite/Stores/ShippingLabelStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ private extension ShippingLabelStore {
}
onCompletion(eligibility.isEligible)
case .failure(let error):
if error as? DotcomError == .noRestRoute {
if case .noRestRoute = error as? DotcomError {
DDLogError("⚠️ Endpoint for shipping label creation eligibility is unreachable for order: \(orderID). WC Shipping plugin may be missing.")
} else {
DDLogError("⛔️ Error checking shipping label creation eligibility for order \(orderID): \(error)")
Expand Down Expand Up @@ -492,7 +492,7 @@ public enum PackageCreationError: Error, Equatable {
return
}
switch dotcomError {
case .unknown(let code, _):
case .unknown(let code, _, _):
guard let errorCode = ErrorCode(rawValue: code) else {
self = .unknown(error: dotcomError.toAnyError)
return
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Yosemite/Stores/SiteStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public enum SiteCreationError: Error, Equatable {
}
case let remoteError as DotcomError:
switch remoteError {
case let .unknown(code, _):
case let .unknown(code, _, _):
switch code {
case "blog_name_exists":
self = .domainExists
Expand Down
2 changes: 1 addition & 1 deletion Modules/Sources/Yosemite/Stores/WooShippingStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private extension WooShippingStore {
}
onCompletion(eligibility.isEligible)
case .failure(let error):
if error as? DotcomError == .noRestRoute {
if case .noRestRoute = error as? DotcomError {
DDLogError("⚠️ Endpoint for shipping label creation eligibility is unreachable for order: \(orderID). WC Shipping plugin may be missing.")
} else {
DDLogError("⛔️ Error checking shipping label creation eligibility for order \(orderID): \(error)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ final public class CommonReaderConfigProvider: CommonReaderConfigProviding {
private extension CardReaderConfigError {
init?(error: Error) {
switch error {
case DotcomError.unknown(code: "store_address_is_incomplete", let message):
case DotcomError.unknown(code: "store_address_is_incomplete", let message, _):
self = .incompleteStoreAddress(adminUrl: URL(string: message ?? ""))
case DotcomError.unknown(code: "postal_code_invalid", _):
case DotcomError.unknown(code: "postal_code_invalid", _, _):
self = .invalidPostalCode
case NetworkError.unacceptableStatusCode(_, let responseData):
guard let responseData,
Expand Down
47 changes: 23 additions & 24 deletions Modules/Sources/Yosemite/Tools/POS/POSOrderService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,35 +143,34 @@ private extension POSOrderService {
_ error: Error,
cart: POSCart
) -> [CartOrderComparison.MissingCartItem]? {
// Check if this is an AFError wrapping a DotcomError or NetworkError
let underlyingError: Error? = {
if let afError = error as? AFError {
return afError.underlyingError
let (code, data) = errorCodeAndData(from: error)
guard let code, isProductValidationError(code: code) else {
return nil
}

guard let variationID = extractVariationID(from: data) else {
return unknownMissingProductsInCart()
}
return createMissingProductInfo(forVariationID: variationID, cart: cart)
}

func errorCodeAndData(from error: Error) -> (code: String?, data: [String: AnyDecodable]?) {
// Unwrap AFError if present
let underlyingError: Error = {
if let afError = error as? AFError, let underlying = afError.underlyingError {
return underlying
}
return error
}()

// Check for DotcomError with product/variation validation error codes
if case .unknown(let code, _) = underlyingError as? DotcomError {
if isProductValidationError(code: code) {
// DotcomError doesn't include data field, fall back to generic error
return extractMissingProductsFromCart()
}
// Extract error code and data from either error type
if case .unknown(let code, _, let data) = underlyingError as? DotcomError {
return (code, data)
}

// Check for NetworkError with product/variation validation error codes
if let networkError = underlyingError as? NetworkError,
let errorCode = networkError.errorCode,
isProductValidationError(code: errorCode) {
// Try to extract variation_id from NetworkError data if available
if let variationID = extractVariationID(from: networkError.errorData) {
return createMissingProductInfo(forVariationID: variationID, cart: cart)
}
// Fall back to generic error if no variation_id in data
return extractMissingProductsFromCart()
if let networkError = underlyingError as? NetworkError {
return (networkError.errorCode, networkError.errorData)
}

return nil
return (nil, nil)
}

/// Extracts variation_id from error data dictionary
Expand Down Expand Up @@ -237,7 +236,7 @@ private extension POSOrderService {

/// Extracts missing products by trying to identify items in cart that might have caused the validation error
/// Since server doesn't tell us which specific products failed, we return generic error info
func extractMissingProductsFromCart() -> [CartOrderComparison.MissingCartItem]? {
func unknownMissingProductsInCart() -> [CartOrderComparison.MissingCartItem] {
// We can't determine which specific products are invalid from the server error
// So we return a generic missing product message with 0 for both IDs (meaning unknown)
// The user will need to remove all products and retry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class CommentRemoteTests: XCTestCase {
return
}

XCTAssert(error == .unauthorized)
XCTAssert(error == .unauthorized())
XCTAssertNil(updatedStatus)

expectation.fulfill()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class JetpackSettingsRemoteTests: XCTestCase {
// When
try await remote.enableJetpackModule(for: sampleSiteID, moduleSlug: "invalidmodule")
}, errorAssert: { error in
(error as? DotcomError) == .unknown(code: "some_updated", message: "Invalid option: invalidmodule.")
(error as? DotcomError) == .unknown(code: "some_updated", message: "Invalid option: invalidmodule.", data: nil)
})
}

Expand Down
Loading