Skip to content

Commit 082c3c9

Browse files
committed
Read error JSON
This way we will not have to look for a println when there is an issue with the tests. This will also help in production because we will log the actual JSON error we get back instead of just the HTTP status.
1 parent 349eb05 commit 082c3c9

File tree

3 files changed

+44
-47
lines changed

3 files changed

+44
-47
lines changed

src/main/kotlin/com/coder/gateway/sdk/BaseCoderRestClient.kt

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import okhttp3.logging.HttpLoggingInterceptor
3636
import org.imgscalr.Scalr
3737
import retrofit2.Retrofit
3838
import retrofit2.converter.gson.GsonConverterFactory
39-
import java.io.IOException
4039
import java.net.HttpURLConnection
4140
import java.net.URL
4241
import java.time.Instant
@@ -103,6 +102,11 @@ open class BaseCoderRestClient(
103102
.build().create(CoderV2RestFacade::class.java)
104103
}
105104

105+
private fun <T> error(action: String, res: retrofit2.Response<T>): String {
106+
val details = res.errorBody()?.charStream()?.use{ it.readText() } ?: "no details provided"
107+
return "Unable to $action: url=$url, code=${res.code()}, details=$details"
108+
}
109+
106110
/**
107111
* Authenticate and load information about the current user and the build
108112
* version.
@@ -122,11 +126,7 @@ open class BaseCoderRestClient(
122126
fun me(): User {
123127
val userResponse = retroRestClient.me().execute()
124128
if (!userResponse.isSuccessful) {
125-
throw AuthenticationResponseException(
126-
"Unable to authenticate to $url: code ${userResponse.code()}, ${
127-
userResponse.message().ifBlank { "has your token expired?" }
128-
}"
129-
)
129+
throw AuthenticationResponseException(error("authenticate", userResponse))
130130
}
131131

132132
return userResponse.body()!!
@@ -139,11 +139,7 @@ open class BaseCoderRestClient(
139139
fun workspaces(): List<Workspace> {
140140
val workspacesResponse = retroRestClient.workspaces("owner:me").execute()
141141
if (!workspacesResponse.isSuccessful) {
142-
throw WorkspaceResponseException(
143-
"Unable to retrieve workspaces from $url: code ${workspacesResponse.code()}, reason: ${
144-
workspacesResponse.message().ifBlank { "no reason provided" }
145-
}"
146-
)
142+
throw WorkspaceResponseException(error("retrieve workspaces", workspacesResponse))
147143
}
148144

149145
return workspacesResponse.body()!!.workspaces
@@ -169,31 +165,23 @@ open class BaseCoderRestClient(
169165
fun resources(workspace: Workspace): List<WorkspaceResource> {
170166
val resourcesResponse = retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID).execute()
171167
if (!resourcesResponse.isSuccessful) {
172-
throw WorkspaceResponseException(
173-
"Unable to retrieve template resources for ${workspace.name} from $url: code ${resourcesResponse.code()}, reason: ${
174-
resourcesResponse.message().ifBlank { "no reason provided" }
175-
}"
176-
)
168+
throw WorkspaceResponseException(error("retrieve resources for ${workspace.name}", resourcesResponse))
177169
}
178170
return resourcesResponse.body()!!
179171
}
180172

181173
fun buildInfo(): BuildInfo {
182174
val buildInfoResponse = retroRestClient.buildInfo().execute()
183175
if (!buildInfoResponse.isSuccessful) {
184-
throw java.lang.IllegalStateException("Unable to retrieve build information for $url, code: ${buildInfoResponse.code()}, reason: ${buildInfoResponse.message().ifBlank { "no reason provided" }}")
176+
throw java.lang.IllegalStateException(error("retrieve build information", buildInfoResponse))
185177
}
186178
return buildInfoResponse.body()!!
187179
}
188180

189181
private fun template(templateID: UUID): Template {
190182
val templateResponse = retroRestClient.template(templateID).execute()
191183
if (!templateResponse.isSuccessful) {
192-
throw TemplateResponseException(
193-
"Unable to retrieve template with ID $templateID from $url, code: ${templateResponse.code()}, reason: ${
194-
templateResponse.message().ifBlank { "no reason provided" }
195-
}"
196-
)
184+
throw TemplateResponseException(error("retrieve template with ID $templateID", templateResponse))
197185
}
198186
return templateResponse.body()!!
199187
}
@@ -202,11 +190,7 @@ open class BaseCoderRestClient(
202190
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.START, null, null, null, null)
203191
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
204192
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
205-
throw WorkspaceResponseException(
206-
"Unable to build workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
207-
buildResponse.message().ifBlank { "no reason provided" }
208-
}"
209-
)
193+
throw WorkspaceResponseException(error("start workspace $workspaceName", buildResponse))
210194
}
211195

212196
return buildResponse.body()!!
@@ -216,11 +200,7 @@ open class BaseCoderRestClient(
216200
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP, null, null, null, null)
217201
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
218202
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
219-
throw WorkspaceResponseException(
220-
"Unable to stop workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
221-
buildResponse.message().ifBlank { "no reason provided" }
222-
}"
223-
)
203+
throw WorkspaceResponseException(error("stop workspace $workspaceName", buildResponse))
224204
}
225205

226206
return buildResponse.body()!!
@@ -245,11 +225,7 @@ open class BaseCoderRestClient(
245225
CreateWorkspaceBuildRequest(template.activeVersionID, WorkspaceTransition.START, null, null, null, null)
246226
val buildResponse = retroRestClient.createWorkspaceBuild(workspaceID, buildRequest).execute()
247227
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
248-
throw WorkspaceResponseException(
249-
"Unable to update workspace $workspaceName on $url, code: ${buildResponse.code()}, reason: ${
250-
buildResponse.message().ifBlank { "no reason provided" }
251-
}"
252-
)
228+
throw WorkspaceResponseException(error("update workspace $workspaceName", buildResponse))
253229
}
254230

255231
return buildResponse.body()!!
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.coder.gateway.sdk.v2.models
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class Validation (
6+
@SerializedName("field") val field: String,
7+
@SerializedName("detail") val detail: String,
8+
)
9+
10+
data class Response (
11+
@SerializedName("message") val message: String,
12+
@SerializedName("detail") val detail: String,
13+
@SerializedName("validations") val validations: List<Validation> = emptyList(),
14+
)

src/test/kotlin/com/coder/gateway/sdk/BaseCoderRestClientTest.kt

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,34 @@ import java.time.Instant
3030
import java.util.UUID
3131
import javax.net.ssl.SSLHandshakeException
3232
import javax.net.ssl.SSLPeerUnverifiedException
33+
import kotlin.test.assertContains
3334
import kotlin.test.assertFailsWith
3435

35-
class BaseHttpHandler(private val method: String,
36+
internal fun toJson(src: Any?): String {
37+
return GsonBuilder().registerTypeAdapter(Instant::class.java, InstantConverter()).create().toJson(src)
38+
}
39+
40+
internal class BaseHttpHandler(private val method: String,
3641
private val handler: (exchange: HttpExchange) -> Unit): HttpHandler {
3742
override fun handle(exchange: HttpExchange) {
3843
try {
3944
if (exchange.requestMethod != method) {
40-
exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_METHOD, 0)
45+
val body = toJson(Response("Not allowed", "Expected $method but got ${exchange.requestMethod}")).toByteArray()
46+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_METHOD, body.size.toLong())
47+
exchange.responseBody.write(body)
4148
} else {
4249
handler(exchange)
4350
if (exchange.responseCode == -1) {
44-
exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, 0)
51+
val body = toJson(Response("Not found", "The requested resource could not be found")).toByteArray()
52+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, body.size.toLong())
53+
exchange.responseBody.write(body)
4554
}
4655
}
4756
} catch (ex: Exception) {
4857
// If we get here it is because of developer error.
49-
println(ex)
50-
exchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, 0)
58+
val body = toJson(Response("Developer error", ex.message ?: "unknown error")).toByteArray()
59+
exchange.sendResponseHeaders(HttpURLConnection.HTTP_BAD_REQUEST, body.size.toLong())
60+
exchange.responseBody.write(body)
5161
}
5262
exchange.close()
5363
}
@@ -65,10 +75,6 @@ class BaseCoderRestClientTest {
6575
return Pair(srv, "http://localhost:" + srv.address.port)
6676
}
6777

68-
private fun toJson(src: Any?): String {
69-
return GsonBuilder().registerTypeAdapter(Instant::class.java, InstantConverter()).create().toJson(src)
70-
}
71-
7278
private fun mockTLSServer(certName: String): Pair<HttpServer, String> {
7379
val srv = HttpsServer.create(InetSocketAddress(0), 0)
7480
val sslContext = sslContextFromPEMs(
@@ -226,10 +232,11 @@ class BaseCoderRestClientTest {
226232

227233
// Fails to stop a non-existent workspace.
228234
val badWorkspace = DataGen.workspace("bad")
229-
assertFailsWith(
235+
val ex = assertFailsWith(
230236
exceptionClass = WorkspaceResponseException::class,
231237
block = { client.updateWorkspace(badWorkspace.id, badWorkspace.name, badWorkspace.latestBuild.transition, badWorkspace.templateID) })
232238
assertEquals(listOf(Pair("stop", badWorkspace.id)), actions)
239+
assertContains(ex.message.toString(), "The requested resource could not be found")
233240
actions.clear()
234241

235242
// When workspace is started it should stop first.

0 commit comments

Comments
 (0)