Skip to content

Commit 918bacd

Browse files
refactor(CoderSDK): share code between Client and AgentClient (#132)
Refactor to address review feedback that `AgentClient` extending the regular `Client` was confusing.
1 parent 8067574 commit 918bacd

File tree

11 files changed

+167
-102
lines changed

11 files changed

+167
-102
lines changed

Coder-Desktop/Coder-Desktop/State.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class AppState: ObservableObject {
122122
let client = Client(url: baseAccessURL!, token: sessionToken!)
123123
do {
124124
_ = try await client.user("me")
125-
} catch let ClientError.api(apiErr) {
125+
} catch let SDKError.api(apiErr) {
126126
// Expired token
127127
if apiErr.statusCode == 401 {
128128
clearSession()

Coder-Desktop/Coder-Desktop/Views/FileSync/FilePicker.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ struct FilePicker: View {
7272
class FilePickerModel: ObservableObject {
7373
@Published var rootEntries: [FilePickerEntryModel] = []
7474
@Published var rootIsLoading: Bool = false
75-
@Published var error: ClientError?
75+
@Published var error: SDKError?
7676

7777
// It's important that `AgentClient` is a reference type (class)
7878
// as we were having performance issues with a struct (unless it was a binding).
@@ -87,7 +87,7 @@ class FilePickerModel: ObservableObject {
8787
rootIsLoading = true
8888
Task {
8989
defer { rootIsLoading = false }
90-
do throws(ClientError) {
90+
do throws(SDKError) {
9191
rootEntries = try await client
9292
.listAgentDirectory(.init(path: [], relativity: .root))
9393
.toModels(client: client)
@@ -149,7 +149,7 @@ class FilePickerEntryModel: Identifiable, Hashable, ObservableObject {
149149

150150
@Published var entries: [FilePickerEntryModel]?
151151
@Published var isLoading = false
152-
@Published var error: ClientError?
152+
@Published var error: SDKError?
153153
@Published private var innerIsExpanded = false
154154
var isExpanded: Bool {
155155
get { innerIsExpanded }
@@ -193,7 +193,7 @@ class FilePickerEntryModel: Identifiable, Hashable, ObservableObject {
193193
innerIsExpanded = true
194194
}
195195
}
196-
do throws(ClientError) {
196+
do throws(SDKError) {
197197
entries = try await client
198198
.listAgentDirectory(.init(path: path, relativity: .root))
199199
.toModels(client: client)

Coder-Desktop/Coder-Desktop/Views/LoginForm.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ enum LoginError: Error {
207207
case invalidURL
208208
case outdatedCoderVersion
209209
case missingServerVersion
210-
case failedAuth(ClientError)
210+
case failedAuth(SDKError)
211211

212212
var description: String {
213213
switch self {

Coder-Desktop/Coder-DesktopTests/FilePickerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct FilePickerTests {
6060
try Mock(
6161
url: url.appendingPathComponent("/api/v0/list-directory"),
6262
statusCode: 200,
63-
data: [.post: Client.encoder.encode(mockResponse)]
63+
data: [.post: CoderSDK.encoder.encode(mockResponse)]
6464
).register()
6565

6666
try await ViewHosting.host(view) {
@@ -88,7 +88,7 @@ struct FilePickerTests {
8888
try Mock(
8989
url: url.appendingPathComponent("/api/v0/list-directory"),
9090
statusCode: 200,
91-
data: [.post: Client.encoder.encode(mockResponse)]
91+
data: [.post: CoderSDK.encoder.encode(mockResponse)]
9292
).register()
9393

9494
try await ViewHosting.host(view) {

Coder-Desktop/Coder-DesktopTests/LoginFormTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct LoginTests {
7979
try Mock(
8080
url: url.appendingPathComponent("/api/v2/buildinfo"),
8181
statusCode: 200,
82-
data: [.get: Client.encoder.encode(buildInfo)]
82+
data: [.get: CoderSDK.encoder.encode(buildInfo)]
8383
).register()
8484
Mock(url: url.appendingPathComponent("/api/v2/users/me"), statusCode: 401, data: [.get: Data()]).register()
8585

@@ -104,13 +104,13 @@ struct LoginTests {
104104
try Mock(
105105
url: url.appendingPathComponent("/api/v2/buildinfo"),
106106
statusCode: 200,
107-
data: [.get: Client.encoder.encode(buildInfo)]
107+
data: [.get: CoderSDK.encoder.encode(buildInfo)]
108108
).register()
109109

110110
try Mock(
111111
url: url.appendingPathComponent("/api/v2/users/me"),
112112
statusCode: 200,
113-
data: [.get: Client.encoder.encode(User(id: UUID(), username: "username"))]
113+
data: [.get: CoderSDK.encoder.encode(User(id: UUID(), username: "username"))]
114114
).register()
115115

116116
try await ViewHosting.host(view) {
@@ -140,13 +140,13 @@ struct LoginTests {
140140
try Mock(
141141
url: url.appendingPathComponent("/api/v2/users/me"),
142142
statusCode: 200,
143-
data: [.get: Client.encoder.encode(user)]
143+
data: [.get: CoderSDK.encoder.encode(user)]
144144
).register()
145145

146146
try Mock(
147147
url: url.appendingPathComponent("/api/v2/buildinfo"),
148148
statusCode: 200,
149-
data: [.get: Client.encoder.encode(buildInfo)]
149+
data: [.get: CoderSDK.encoder.encode(buildInfo)]
150150
).register()
151151

152152
try await ViewHosting.host(view) {
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
public final class AgentClient: Sendable {
2-
let client: Client
2+
let agentURL: URL
33

44
public init(agentHost: String) {
5-
client = Client(url: URL(string: "http://\(agentHost):4")!)
5+
agentURL = URL(string: "http://\(agentHost):4")!
6+
}
7+
8+
func request(
9+
_ path: String,
10+
method: HTTPMethod
11+
) async throws(SDKError) -> HTTPResponse {
12+
try await CoderSDK.request(baseURL: agentURL, path: path, method: method)
13+
}
14+
15+
func request(
16+
_ path: String,
17+
method: HTTPMethod,
18+
body: some Encodable & Sendable
19+
) async throws(SDKError) -> HTTPResponse {
20+
try await CoderSDK.request(baseURL: agentURL, path: path, method: method, body: body)
621
}
722
}

Coder-Desktop/CoderSDK/AgentLS.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
public extension AgentClient {
2-
func listAgentDirectory(_ req: LSRequest) async throws(ClientError) -> LSResponse {
3-
let res = try await client.request("/api/v0/list-directory", method: .post, body: req)
2+
func listAgentDirectory(_ req: LSRequest) async throws(SDKError) -> LSResponse {
3+
let res = try await request("/api/v0/list-directory", method: .post, body: req)
44
guard res.resp.statusCode == 200 else {
5-
throw client.responseAsError(res)
5+
throw responseAsError(res)
66
}
7-
return try client.decode(LSResponse.self, from: res.data)
7+
return try decode(LSResponse.self, from: res.data)
88
}
99
}
1010

Coder-Desktop/CoderSDK/Client.swift

Lines changed: 129 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -11,95 +11,38 @@ public struct Client: Sendable {
1111
self.headers = headers
1212
}
1313

14-
static let decoder: JSONDecoder = {
15-
var dec = JSONDecoder()
16-
dec.dateDecodingStrategy = .iso8601withOptionalFractionalSeconds
17-
return dec
18-
}()
19-
20-
static let encoder: JSONEncoder = {
21-
var enc = JSONEncoder()
22-
enc.dateEncodingStrategy = .iso8601withFractionalSeconds
23-
return enc
24-
}()
25-
26-
private func doRequest(
27-
path: String,
28-
method: HTTPMethod,
29-
body: Data? = nil
30-
) async throws(ClientError) -> HTTPResponse {
31-
let url = url.appendingPathComponent(path)
32-
var req = URLRequest(url: url)
33-
if let token { req.addValue(token, forHTTPHeaderField: Headers.sessionToken) }
34-
req.httpMethod = method.rawValue
35-
for header in headers {
36-
req.addValue(header.value, forHTTPHeaderField: header.name)
37-
}
38-
req.httpBody = body
39-
let data: Data
40-
let resp: URLResponse
41-
do {
42-
(data, resp) = try await URLSession.shared.data(for: req)
43-
} catch {
44-
throw .network(error)
45-
}
46-
guard let httpResponse = resp as? HTTPURLResponse else {
47-
throw .unexpectedResponse(String(data: data, encoding: .utf8) ?? "<non-utf8 data>")
48-
}
49-
return HTTPResponse(resp: httpResponse, data: data, req: req)
50-
}
51-
5214
func request(
5315
_ path: String,
5416
method: HTTPMethod,
5517
body: some Encodable & Sendable
56-
) async throws(ClientError) -> HTTPResponse {
57-
let encodedBody: Data?
58-
do {
59-
encodedBody = try Client.encoder.encode(body)
60-
} catch {
61-
throw .encodeFailure(error)
18+
) async throws(SDKError) -> HTTPResponse {
19+
var headers = headers
20+
if let token {
21+
headers += [.init(name: Headers.sessionToken, value: token)]
6222
}
63-
return try await doRequest(path: path, method: method, body: encodedBody)
23+
return try await CoderSDK.request(
24+
baseURL: url,
25+
path: path,
26+
method: method,
27+
headers: headers,
28+
body: body
29+
)
6430
}
6531

6632
func request(
6733
_ path: String,
6834
method: HTTPMethod
69-
) async throws(ClientError) -> HTTPResponse {
70-
try await doRequest(path: path, method: method)
71-
}
72-
73-
func responseAsError(_ resp: HTTPResponse) -> ClientError {
74-
do {
75-
let body = try decode(Response.self, from: resp.data)
76-
let out = APIError(
77-
response: body,
78-
statusCode: resp.resp.statusCode,
79-
method: resp.req.httpMethod!,
80-
url: resp.req.url!
81-
)
82-
return .api(out)
83-
} catch {
84-
return .unexpectedResponse(String(data: resp.data, encoding: .utf8) ?? "<non-utf8 data>")
85-
}
86-
}
87-
88-
// Wrapper around JSONDecoder.decode that displays useful error messages from `DecodingError`.
89-
func decode<T>(_: T.Type, from data: Data) throws(ClientError) -> T where T: Decodable {
90-
do {
91-
return try Client.decoder.decode(T.self, from: data)
92-
} catch let DecodingError.keyNotFound(_, context) {
93-
throw .unexpectedResponse("Key not found: \(context.debugDescription)")
94-
} catch let DecodingError.valueNotFound(_, context) {
95-
throw .unexpectedResponse("Value not found: \(context.debugDescription)")
96-
} catch let DecodingError.typeMismatch(_, context) {
97-
throw .unexpectedResponse("Type mismatch: \(context.debugDescription)")
98-
} catch let DecodingError.dataCorrupted(context) {
99-
throw .unexpectedResponse("Data corrupted: \(context.debugDescription)")
100-
} catch {
101-
throw .unexpectedResponse(String(data: data.prefix(1024), encoding: .utf8) ?? "<non-utf8 data>")
35+
) async throws(SDKError) -> HTTPResponse {
36+
var headers = headers
37+
if let token {
38+
headers += [.init(name: Headers.sessionToken, value: token)]
10239
}
40+
return try await CoderSDK.request(
41+
baseURL: url,
42+
path: path,
43+
method: method,
44+
headers: headers
45+
)
10346
}
10447
}
10548

@@ -133,7 +76,7 @@ public struct FieldValidation: Decodable, Sendable {
13376
let detail: String
13477
}
13578

136-
public enum ClientError: Error {
79+
public enum SDKError: Error {
13780
case api(APIError)
13881
case network(any Error)
13982
case unexpectedResponse(String)
@@ -154,3 +97,110 @@ public enum ClientError: Error {
15497

15598
public var localizedDescription: String { description }
15699
}
100+
101+
let decoder: JSONDecoder = {
102+
var dec = JSONDecoder()
103+
dec.dateDecodingStrategy = .iso8601withOptionalFractionalSeconds
104+
return dec
105+
}()
106+
107+
let encoder: JSONEncoder = {
108+
var enc = JSONEncoder()
109+
enc.dateEncodingStrategy = .iso8601withFractionalSeconds
110+
return enc
111+
}()
112+
113+
func doRequest(
114+
baseURL: URL,
115+
path: String,
116+
method: HTTPMethod,
117+
headers: [HTTPHeader] = [],
118+
body: Data? = nil
119+
) async throws(SDKError) -> HTTPResponse {
120+
let url = baseURL.appendingPathComponent(path)
121+
var req = URLRequest(url: url)
122+
req.httpMethod = method.rawValue
123+
for header in headers {
124+
req.addValue(header.value, forHTTPHeaderField: header.name)
125+
}
126+
req.httpBody = body
127+
let data: Data
128+
let resp: URLResponse
129+
do {
130+
(data, resp) = try await URLSession.shared.data(for: req)
131+
} catch {
132+
throw .network(error)
133+
}
134+
guard let httpResponse = resp as? HTTPURLResponse else {
135+
throw .unexpectedResponse(String(data: data, encoding: .utf8) ?? "<non-utf8 data>")
136+
}
137+
return HTTPResponse(resp: httpResponse, data: data, req: req)
138+
}
139+
140+
func request(
141+
baseURL: URL,
142+
path: String,
143+
method: HTTPMethod,
144+
headers: [HTTPHeader] = [],
145+
body: some Encodable & Sendable
146+
) async throws(SDKError) -> HTTPResponse {
147+
let encodedBody: Data
148+
do {
149+
encodedBody = try encoder.encode(body)
150+
} catch {
151+
throw .encodeFailure(error)
152+
}
153+
return try await doRequest(
154+
baseURL: baseURL,
155+
path: path,
156+
method: method,
157+
headers: headers,
158+
body: encodedBody
159+
)
160+
}
161+
162+
func request(
163+
baseURL: URL,
164+
path: String,
165+
method: HTTPMethod,
166+
headers: [HTTPHeader] = []
167+
) async throws(SDKError) -> HTTPResponse {
168+
try await doRequest(
169+
baseURL: baseURL,
170+
path: path,
171+
method: method,
172+
headers: headers
173+
)
174+
}
175+
176+
func responseAsError(_ resp: HTTPResponse) -> SDKError {
177+
do {
178+
let body = try decode(Response.self, from: resp.data)
179+
let out = APIError(
180+
response: body,
181+
statusCode: resp.resp.statusCode,
182+
method: resp.req.httpMethod!,
183+
url: resp.req.url!
184+
)
185+
return .api(out)
186+
} catch {
187+
return .unexpectedResponse(String(data: resp.data, encoding: .utf8) ?? "<non-utf8 data>")
188+
}
189+
}
190+
191+
// Wrapper around JSONDecoder.decode that displays useful error messages from `DecodingError`.
192+
func decode<T: Decodable>(_: T.Type, from data: Data) throws(SDKError) -> T {
193+
do {
194+
return try decoder.decode(T.self, from: data)
195+
} catch let DecodingError.keyNotFound(_, context) {
196+
throw .unexpectedResponse("Key not found: \(context.debugDescription)")
197+
} catch let DecodingError.valueNotFound(_, context) {
198+
throw .unexpectedResponse("Value not found: \(context.debugDescription)")
199+
} catch let DecodingError.typeMismatch(_, context) {
200+
throw .unexpectedResponse("Type mismatch: \(context.debugDescription)")
201+
} catch let DecodingError.dataCorrupted(context) {
202+
throw .unexpectedResponse("Data corrupted: \(context.debugDescription)")
203+
} catch {
204+
throw .unexpectedResponse(String(data: data.prefix(1024), encoding: .utf8) ?? "<non-utf8 data>")
205+
}
206+
}

Coder-Desktop/CoderSDK/Deployment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22

33
public extension Client {
4-
func buildInfo() async throws(ClientError) -> BuildInfoResponse {
4+
func buildInfo() async throws(SDKError) -> BuildInfoResponse {
55
let res = try await request("/api/v2/buildinfo", method: .get)
66
guard res.resp.statusCode == 200 else {
77
throw responseAsError(res)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy