-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNetworkService.swift
121 lines (106 loc) · 4.8 KB
/
NetworkService.swift
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
//
// NetworkService.swift
// Networking
//
// Created by Viktor Gidlöf.
//
import Foundation
public let name = "Networking"
public let version = "0.9.1"
public enum Network {
/// A network service object used to make requests to the backend.
public final class Service {
// MARK: Private properties
private let server: ServerConfig
private let decoder: JSONDecoder
private let session: URLSession
// MARK: - Init
/// Initialize the network service
/// - parameters:
/// - server: The given server configuration
/// - session: The given URLSession object. Defaults to the shared instance.
/// - decoder: A default json decoder object
public init(server: ServerConfig, session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) {
self.session = session
self.decoder = decoder
self.server = server
}
}
}
// MARK: - Public functions
public extension Network.Service {
/// Send a request and decode the response into a data model object
/// - parameters:
/// - request: The request to send over the network
/// - logResponse: A boolean value that determines if the json response should be printed to the console. Defaults to false.
/// - throws: An error if the request fails for any reason
/// - returns: The decoded data model object
func request<DataModel: Decodable>(_ request: Requestable, logResponse: Bool = false) async throws -> DataModel {
let (data, _) = try await makeDataRequest(request, logResponse: logResponse)
return try decoder.decode(DataModel.self, from: data)
}
/// Send a request and return the raw response data
/// - parameters:
/// - request: The request to send over the network
/// - logResponse: A boolean value that determines if the json response should be printed to the console. Defaults to false.
/// - throws: An error if the request fails for any reason
/// - returns: The raw response data
func data(_ request: Requestable, logResponse: Bool = false) async throws -> Data {
let (data, _) = try await makeDataRequest(request, logResponse: logResponse)
return data
}
/// Send a request and return the HTTP status code
/// - parameters:
/// - request: The request to send over the network
/// - logResponse: A boolean value that determines if the json response should be printed to the console. Defaults to false.
/// - throws: An error if the request fails for any reason
/// - returns: The HTTP status code
func response(_ request: Requestable, logResponse: Bool = false) async throws -> HTTP.StatusCode {
let (_, response) = try await makeDataRequest(request, logResponse: logResponse)
guard let httpResponse = response as? HTTPURLResponse else { return .unknown }
return HTTP.StatusCode(rawValue: httpResponse.statusCode) ?? .unknown
}
/// Creates a new instance of `Network.Service.Downloader` configured with the specified URL.
/// - Parameter url: The `URL` from which the downloader will retrieve data.
/// - Returns: A configured `Network.Service.Downloader` instance for downloading data from the given URL.
func downloader(url: URL) -> Network.Service.Downloader {
Network.Service.Downloader(url: url)
}
}
// MARK: - Private functions
private extension Network.Service {
func makeDataRequest(_ request: Requestable, logResponse: Bool) async throws -> (Data, URLResponse) {
let urlRequest = try request.configure(withServer: server)
let (data, response) = try await session.data(for: urlRequest)
if logResponse {
String.logResponse((data, response), printJSON: logResponse)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.badServerResponse(-1)
}
guard (HTTP.StatusCode.ok.rawValue ... HTTP.StatusCode.iMUsed.rawValue).contains(httpResponse.statusCode) else {
throw NetworkError.badServerResponse(httpResponse.statusCode)
}
return (data, response)
}
}
public extension Network.Service {
enum NetworkError: LocalizedError {
case invalidURL
case badServerResponse(Int)
case decodingError(Error)
case networkError(Error)
public var errorDescription: String? {
switch self {
case .invalidURL:
"Invalid URL"
case .badServerResponse(let code):
"Server returned status code: \(code)"
case .decodingError(let error):
"Failed to decode data: \(error.localizedDescription)"
case .networkError(let error):
"Network error: \(error.localizedDescription)"
}
}
}
}