diff --git a/Coder Desktop/Coder DesktopTests/LoginFormTests.swift b/Coder Desktop/Coder DesktopTests/LoginFormTests.swift index e966178..b58f817 100644 --- a/Coder Desktop/Coder DesktopTests/LoginFormTests.swift +++ b/Coder Desktop/Coder DesktopTests/LoginFormTests.swift @@ -93,18 +93,7 @@ struct LoginTests { let user = User( id: UUID(), - username: "admin", - avatar_url: "", - name: "admin", - email: "admin@coder.com", - created_at: Date.now, - updated_at: Date.now, - last_seen_at: Date.now, - status: "active", - login_type: "none", - theme_preference: "dark", - organization_ids: [], - roles: [] + username: "admin" ) try Mock( diff --git a/Coder Desktop/CoderSDK/Client.swift b/Coder Desktop/CoderSDK/Client.swift index 43e9b59..85bc8f3 100644 --- a/Coder Desktop/CoderSDK/Client.swift +++ b/Coder Desktop/CoderSDK/Client.swift @@ -44,7 +44,7 @@ public struct Client { throw .network(error) } guard let httpResponse = resp as? HTTPURLResponse else { - throw .unexpectedResponse(data) + throw .unexpectedResponse(String(data: data, encoding: .utf8) ?? "") } return HTTPResponse(resp: httpResponse, data: data, req: req) } @@ -72,7 +72,7 @@ public struct Client { func responseAsError(_ resp: HTTPResponse) -> ClientError { do { - let body = try Client.decoder.decode(Response.self, from: resp.data) + let body = try decode(Response.self, from: resp.data) let out = APIError( response: body, statusCode: resp.resp.statusCode, @@ -81,7 +81,24 @@ public struct Client { ) return .api(out) } catch { - return .unexpectedResponse(resp.data.prefix(1024)) + return .unexpectedResponse(String(data: resp.data, encoding: .utf8) ?? "") + } + } + + // Wrapper around JSONDecoder.decode that displays useful error messages from `DecodingError`. + func decode(_: T.Type, from data: Data) throws(ClientError) -> T where T: Decodable { + do { + return try Client.decoder.decode(T.self, from: data) + } catch let DecodingError.keyNotFound(_, context) { + throw .unexpectedResponse("Key not found: \(context.debugDescription)") + } catch let DecodingError.valueNotFound(_, context) { + throw .unexpectedResponse("Value not found: \(context.debugDescription)") + } catch let DecodingError.typeMismatch(_, context) { + throw .unexpectedResponse("Type mismatch: \(context.debugDescription)") + } catch let DecodingError.dataCorrupted(context) { + throw .unexpectedResponse("Data corrupted: \(context.debugDescription)") + } catch { + throw .unexpectedResponse(String(data: data.prefix(1024), encoding: .utf8) ?? "") } } } @@ -119,7 +136,7 @@ public struct FieldValidation: Decodable, Sendable { public enum ClientError: Error { case api(APIError) case network(any Error) - case unexpectedResponse(Data) + case unexpectedResponse(String) case encodeFailure(any Error) public var description: String { @@ -129,7 +146,7 @@ public enum ClientError: Error { case let .network(error): error.localizedDescription case let .unexpectedResponse(data): - "Unexpected or non HTTP response: \(data)" + "Unexpected response: \(data)" case let .encodeFailure(error): "Failed to encode body: \(error.localizedDescription)" } diff --git a/Coder Desktop/CoderSDK/Deployment.swift b/Coder Desktop/CoderSDK/Deployment.swift index 8144c0a..8357a7e 100644 --- a/Coder Desktop/CoderSDK/Deployment.swift +++ b/Coder Desktop/CoderSDK/Deployment.swift @@ -6,24 +6,12 @@ public extension Client { guard res.resp.statusCode == 200 else { throw responseAsError(res) } - do { - return try Client.decoder.decode(BuildInfoResponse.self, from: res.data) - } catch { - throw .unexpectedResponse(res.data.prefix(1024)) - } + return try decode(BuildInfoResponse.self, from: res.data) } } -public struct BuildInfoResponse: Encodable, Decodable, Equatable, Sendable { - public let external_url: String +public struct BuildInfoResponse: Codable, Equatable, Sendable { public let version: String - public let dashboard_url: String - public let telemetry: Bool - public let workspace_proxy: Bool - public let agent_api_version: String - public let provisioner_api_version: String - public let upgrade_message: String - public let deployment_id: String // `version` in the form `[0-9]+.[0-9]+.[0-9]+` public var semver: String? { diff --git a/Coder Desktop/CoderSDK/User.swift b/Coder Desktop/CoderSDK/User.swift index e7f85f4..ca1bbf7 100644 --- a/Coder Desktop/CoderSDK/User.swift +++ b/Coder Desktop/CoderSDK/User.swift @@ -6,68 +6,19 @@ public extension Client { guard res.resp.statusCode == 200 else { throw responseAsError(res) } - do { - return try Client.decoder.decode(User.self, from: res.data) - } catch { - throw .unexpectedResponse(res.data.prefix(1024)) - } + return try decode(User.self, from: res.data) } } public struct User: Encodable, Decodable, Equatable, Sendable { public let id: UUID public let username: String - public let avatar_url: String - public let name: String - public let email: String - public let created_at: Date - public let updated_at: Date - public let last_seen_at: Date - public let status: String - public let login_type: String - public let theme_preference: String - public let organization_ids: [UUID] - public let roles: [Role] public init( id: UUID, - username: String, - avatar_url: String, - name: String, - email: String, - created_at: Date, - updated_at: Date, - last_seen_at: Date, - status: String, - login_type: String, - theme_preference: String, - organization_ids: [UUID], - roles: [Role] + username: String ) { self.id = id self.username = username - self.avatar_url = avatar_url - self.name = name - self.email = email - self.created_at = created_at - self.updated_at = updated_at - self.last_seen_at = last_seen_at - self.status = status - self.login_type = login_type - self.theme_preference = theme_preference - self.organization_ids = organization_ids - self.roles = roles - } -} - -public struct Role: Encodable, Decodable, Equatable, Sendable { - public let name: String - public let display_name: String - public let organization_id: UUID? - - public init(name: String, display_name: String, organization_id: UUID?) { - self.name = name - self.display_name = display_name - self.organization_id = organization_id } } diff --git a/Coder Desktop/CoderSDKTests/CoderSDKTests.swift b/Coder Desktop/CoderSDKTests/CoderSDKTests.swift index 8184730..e7675b7 100644 --- a/Coder Desktop/CoderSDKTests/CoderSDKTests.swift +++ b/Coder Desktop/CoderSDKTests/CoderSDKTests.swift @@ -7,23 +7,9 @@ import Testing struct CoderSDKTests { @Test func user() async throws { - let now = Date.now let user = User( id: UUID(), - username: "johndoe", - avatar_url: "https://example.com/img.png", - name: "John Doe", - email: "john.doe@example.com", - created_at: now, - updated_at: now, - last_seen_at: now, - status: "active", - login_type: "email", - theme_preference: "dark", - organization_ids: [UUID()], - roles: [ - Role(name: "user", display_name: "User", organization_id: UUID()), - ] + username: "johndoe" ) let url = URL(string: "https://example.com")! @@ -50,15 +36,7 @@ struct CoderSDKTests { @Test func buildInfo() async throws { let buildInfo = BuildInfoResponse( - external_url: "https://example.com", - version: "v2.18.2-devel+630fd7c0a", - dashboard_url: "https://example.com/dashboard", - telemetry: true, - workspace_proxy: false, - agent_api_version: "1.0", - provisioner_api_version: "1.2", - upgrade_message: "foo", - deployment_id: UUID().uuidString + version: "v2.18.2-devel+630fd7c0a" ) let url = URL(string: "https://example.com")!