Skip to content

Commit b68ef1d

Browse files
authored
Handle non-JSON Databricks error responses (#247)
1 parent c69bb95 commit b68ef1d

5 files changed

Lines changed: 102 additions & 9 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# brickster (development version)
22

33
- Fixed `dbWriteTable()` and `dbAppendTable()` standard-path writes for binary columns, which now use Databricks `BINARY` types and `X'...'` literals when no staging volume is configured (#245)
4+
- `dbConnect()` now preserves Databricks API error details when its validation query fails
5+
- `db_perform_request()` and `db_perform_response()` now preserve useful error messages when Databricks returns non-JSON error bodies such as empty, plain-text, or HTML responses (#242)
46
- Fixed `git_source()` erroring when `type` was left at its default
57
- Fixed Unity Catalog volume file requests so `db_volume_*` paths containing spaces are encoded correctly (#231)
68
- `db_cluster_events()` now forwards the `event_types` argument to the API, which was previously ignored

R/databricks-dbi.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ setMethod(
168168
},
169169
error = function(e) {
170170
cli::cli_abort(
171-
"Failed to connect to warehouse {.val {warehouse_id}}: {e$message}"
171+
"Failed to connect to warehouse {.val {warehouse_id}}.",
172+
parent = e,
173+
call = NULL
172174
)
173175
}
174176
)

R/request-helpers.R

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,29 @@ db_request <- function(
9090
#'
9191
#' @family Request Helpers
9292
db_req_error_body <- function(resp) {
93-
json <- resp |> httr2::resp_body_json()
94-
# if there is "message":
95-
if ("message" %in% names(json)) {
96-
paste(json$error_code, json$message, sep = ": ")
97-
} else if (length(json) == 1) {
98-
json[[1]]
99-
} else {
100-
paste(json, collapse = " ")
93+
content_type <- httr2::resp_content_type(resp)
94+
95+
if (identical(content_type, "application/json")) {
96+
json <- httr2::resp_body_json(resp)
97+
98+
# if there is "message":
99+
if ("message" %in% names(json)) {
100+
return(paste(json$error_code, json$message, sep = ": "))
101+
} else {
102+
return(paste(json, collapse = " "))
103+
}
101104
}
105+
106+
body <- tryCatch(
107+
httr2::resp_body_string(resp),
108+
error = function(cnd) NULL
109+
)
110+
111+
if (is.null(body) || !nzchar(trimws(body))) {
112+
return(httr2::resp_status_desc(resp))
113+
}
114+
115+
trimws(body)
102116
}
103117

104118
#' Perform Databricks API Request

tests/testthat/test-databricks-dbi-offline-helpers.R

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,35 @@ test_that("dbConnect validates tuning inputs and persists connection settings",
9696
)
9797
})
9898

99+
test_that("dbConnect preserves validation query error details", {
100+
drv <- DatabricksSQL()
101+
102+
local_mocked_bindings(
103+
db_sql_query = function(...) {
104+
cli::cli_abort(c(
105+
"HTTP 403 Forbidden.",
106+
"i" = "PERMISSION_DENIED: You do not have permission to use the SQL Warehouse."
107+
))
108+
},
109+
.package = "brickster"
110+
)
111+
112+
expect_error(
113+
dbConnect(
114+
drv,
115+
warehouse_id = "wh-1",
116+
host = "mock_host",
117+
token = "mock_token"
118+
),
119+
regexp = paste(
120+
"Failed to connect to warehouse",
121+
"HTTP 403 Forbidden",
122+
"PERMISSION_DENIED",
123+
sep = ".*"
124+
)
125+
)
126+
})
127+
99128
test_that("dbWriteTable validates user input", {
100129
con <- make_dbi_test_con(staging_volume = "/Volumes/c/s/v")
101130
value <- data.frame(x = 1:3)

tests/testthat/test-request-helpers.R

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ local_clear_auth_env <- function() {
1515
)
1616
}
1717

18+
local_error_response <- function(body, content_type = "application/json", status = 400) {
19+
httr2::response(
20+
status_code = status,
21+
headers = list("content-type" = content_type),
22+
body = charToRaw(body)
23+
)
24+
}
25+
1826
test_that("request helpers - building requests", {
1927
local_clear_auth_env()
2028

@@ -52,6 +60,44 @@ test_that("request helpers - building requests", {
5260
expect_null(db_request_json(NULL))
5361
})
5462

63+
test_that("request error body handles standard Databricks JSON errors", {
64+
resp <- local_error_response(
65+
paste0(
66+
'{"error_code":"PERMISSION_DENIED",',
67+
'"message":"You do not have permission to use the SQL Warehouse."}'
68+
),
69+
status = 403
70+
)
71+
72+
expect_identical(
73+
db_req_error_body(resp),
74+
"PERMISSION_DENIED: You do not have permission to use the SQL Warehouse."
75+
)
76+
})
77+
78+
test_that("request error body handles non-JSON Databricks errors", {
79+
text_html_resp <- local_error_response(
80+
"Invalid Token",
81+
content_type = "text/html; charset=utf-8"
82+
)
83+
string_resp <- local_error_response(
84+
"DEADLINE_EXCEEDED: Deadline exceeded when awaiting statement ID",
85+
content_type = "text/plain"
86+
)
87+
empty_resp <- local_error_response(
88+
"",
89+
content_type = "text/html; charset=utf-8",
90+
status = 504
91+
)
92+
93+
expect_identical(db_req_error_body(text_html_resp), "Invalid Token")
94+
expect_identical(
95+
db_req_error_body(string_resp),
96+
"DEADLINE_EXCEEDED: Deadline exceeded when awaiting statement ID"
97+
)
98+
expect_identical(db_req_error_body(empty_resp), "Gateway Timeout")
99+
})
100+
55101
test_that("request helpers - m2m auth flow", {
56102
local_clear_auth_env()
57103

0 commit comments

Comments
 (0)