summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/RESTError.swift
blob: 08e562c8664d07e7b646a6f172d4e5914b650143 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//
//  RESTError.swift
//  RESTError
//
//  Created by pronebird on 27/07/2021.
//  Copyright © 2021 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes

extension REST {
    /// An error type returned by REST API classes.
    public enum Error: LocalizedError, WrappingError {
        /// Failure to create URL request.
        case createURLRequest(Swift.Error)

        /// Network failure.
        case network(URLError)

        /// Failure to handle response.
        case unhandledResponse(_ statusCode: Int, _ serverResponse: ServerErrorResponse?)

        /// Failure to decode server response.
        case decodeResponse(Swift.Error)

        /// Failure to transit URL request via selected transport implementation.
        case transport(Swift.Error)

        public var errorDescription: String? {
            switch self {
            case .createURLRequest:
                return "Failure to create URL request."

            case .network:
                return "Network error."

            case let .unhandledResponse(statusCode, serverResponse):
                var str = "Failure to handle server response: HTTP/\(statusCode)."

                if let code = serverResponse?.code {
                    str += " Error code: \(code.rawValue)."
                }

                if let detail = serverResponse?.detail {
                    str += " Detail: \(detail)"
                }

                return str

            case .decodeResponse:
                return "Failure to decode response."

            case .transport:
                return "Transport error."
            }
        }

        public var underlyingError: Swift.Error? {
            switch self {
            case let .network(error):
                return error
            case let .createURLRequest(error):
                return error
            case let .decodeResponse(error):
                return error
            case let .transport(error):
                return error
            case .unhandledResponse:
                return nil
            }
        }

        public func compareErrorCode(_ code: ServerResponseCode) -> Bool {
            if case let .unhandledResponse(_, serverResponse) = self {
                return serverResponse?.code == code
            } else {
                return false
            }
        }
    }

    public struct ServerErrorResponse: Decodable {
        public let code: ServerResponseCode
        public let detail: String?

        private enum CodingKeys: String, CodingKey {
            case code, detail, error
        }

        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let rawValue = try container.decode(String.self, forKey: .code)

            code = ServerResponseCode(rawValue: rawValue)
            detail = try container.decodeIfPresent(String.self, forKey: .detail)
                ?? container.decodeIfPresent(String.self, forKey: .error)
        }
    }

    public struct ServerResponseCode: RawRepresentable, Equatable {
        public static let invalidAccount = ServerResponseCode(rawValue: "INVALID_ACCOUNT")
        public static let keyLimitReached = ServerResponseCode(rawValue: "KEY_LIMIT_REACHED")
        public static let publicKeyNotFound = ServerResponseCode(rawValue: "PUBKEY_NOT_FOUND")
        public static let publicKeyInUse = ServerResponseCode(rawValue: "PUBKEY_IN_USE")
        public static let maxDevicesReached = ServerResponseCode(rawValue: "MAX_DEVICES_REACHED")
        public static let invalidAccessToken = ServerResponseCode(rawValue: "INVALID_ACCESS_TOKEN")
        public static let deviceNotFound = ServerResponseCode(rawValue: "DEVICE_NOT_FOUND")
        public static let serviceUnavailable = ServerResponseCode(rawValue: "SERVICE_UNAVAILABLE")
        public static let tooManyRequests = ServerResponseCode(rawValue: "TOO_MANY_REQUESTS")

        public let rawValue: String
        public init(rawValue: String) {
            self.rawValue = rawValue
        }
    }

    public struct NoTransportError: LocalizedError {
        public var errorDescription: String? {
            return "Transport is not configured."
        }
    }
}