@@ -3,7 +3,7 @@ import Foundation
3
3
4
4
protocol Client {
5
5
init ( url: URL , token: String ? )
6
- func user( _ ident: String ) async throws -> User
6
+ func user( _ ident: String ) async throws ( ClientError ) -> User
7
7
}
8
8
9
9
struct CoderClient : Client {
@@ -25,38 +25,122 @@ struct CoderClient: Client {
25
25
func request< T: Encodable > (
26
26
_ path: String ,
27
27
method: HTTPMethod ,
28
- body: T
29
- ) async -> DataResponse < Data , AFError > {
28
+ body: T ? = nil
29
+ ) async throws ( ClientError ) -> HTTPResponse {
30
30
let url = self . url. appendingPathComponent ( path)
31
- let headers : HTTPHeaders = [ Headers . sessionToken: token ?? " " ]
32
- return await AF . request (
31
+ let headers : HTTPHeaders ? = token . map { [ Headers . sessionToken: $0 ] }
32
+ let out = await AF . request (
33
33
url,
34
34
method: method,
35
35
parameters: body,
36
- encoder: JSONParameterEncoder . default,
37
36
headers: headers
38
37
) . serializingData ( ) . response
38
+ guard let response = out. response else {
39
+ throw ClientError . noResponse
40
+ }
41
+ switch out. result {
42
+ case . success( let data) :
43
+ return HTTPResponse ( resp: response, data: data, req: out. request)
44
+ case . failure:
45
+ throw ClientError . badResponse
46
+ }
39
47
}
40
48
41
49
func request(
42
50
_ path: String ,
43
51
method: HTTPMethod
44
- ) async -> DataResponse < Data , AFError > {
52
+ ) async throws ( ClientError ) -> HTTPResponse {
45
53
let url = self . url. appendingPathComponent ( path)
46
- let headers : HTTPHeaders = [ Headers . sessionToken: token ?? " " ]
47
- return await AF . request (
54
+ let headers : HTTPHeaders ? = token . map { [ Headers . sessionToken: $0 ] }
55
+ let out = await AF . request (
48
56
url,
49
57
method: method,
50
58
headers: headers
51
59
) . serializingData ( ) . response
60
+ guard let response = out. response else {
61
+ throw ClientError . noResponse
62
+ }
63
+ switch out. result {
64
+ case . success( let data) :
65
+ return HTTPResponse ( resp: response, data: data, req: out. request)
66
+ case . failure:
67
+ throw ClientError . badResponse
68
+ }
52
69
}
70
+
71
+ func responseAsError( _ resp: HTTPResponse ) throws ( ClientError) -> APIError {
72
+ do {
73
+ let body = try CoderClient . decoder. decode ( Response . self, from: resp. data)
74
+ return APIError (
75
+ response: body,
76
+ statusCode: resp. resp. statusCode,
77
+ method: resp. req? . httpMethod,
78
+ url: resp. req? . url
79
+ )
80
+ } catch {
81
+ throw ClientError . badResponse
82
+ }
83
+ }
84
+
85
+ enum Headers {
86
+ static let sessionToken = " Coder-Session-Token "
87
+ }
88
+
89
+ }
90
+
91
+ struct HTTPResponse {
92
+ let resp : HTTPURLResponse
93
+ let data : Data
94
+ let req : URLRequest ?
95
+ }
96
+
97
+ struct APIError : Decodable {
98
+ let response : Response
99
+ let statusCode : Int
100
+ let method : String ?
101
+ let url : URL ?
102
+
103
+ var description : String {
104
+ var components : [ String ] = [ ]
105
+ if let method = method, let url = url {
106
+ components. append ( " \( method) \( url. absoluteString) " )
107
+ }
108
+ components. append ( " Unexpected status code \( statusCode) : \n \( response. message) " )
109
+ if let detail = response. detail {
110
+ components. append ( " \t Error: \( detail) " )
111
+ }
112
+ if let validations = response. validations, !validations. isEmpty {
113
+ let validationMessages = validations. map { " \t \( $0. field) : \( $0. detail) " }
114
+ components. append ( contentsOf: validationMessages)
115
+ }
116
+ return components. joined ( separator: " \n " )
117
+ }
118
+ }
119
+
120
+ struct Response : Decodable {
121
+ let message : String
122
+ let detail : String ?
123
+ let validations : [ ValidationError ] ?
124
+ }
125
+
126
+ struct ValidationError : Decodable {
127
+ let field : String
128
+ let detail : String
53
129
}
54
130
55
131
enum ClientError : Error {
56
- case unexpectedStatusCode
132
+ case apiError ( APIError )
57
133
case badResponse
58
- }
134
+ case noResponse
59
135
60
- enum Headers {
61
- static let sessionToken = " Coder-Session-Token "
136
+ var description : String {
137
+ switch self {
138
+ case . apiError( let error) :
139
+ return error. description
140
+ case . badResponse:
141
+ return " Bad response "
142
+ case . noResponse:
143
+ return " No response "
144
+ }
145
+ }
62
146
}
0 commit comments