diff --git a/R/crosswalks.R b/R/crosswalks.R index 23bc162..df59e72 100644 --- a/R/crosswalks.R +++ b/R/crosswalks.R @@ -27,7 +27,7 @@ onet_crosswalk_military <- function(keyword, start = 1, end = 20) { } resp <- onet_request("online/crosswalks/military", - keyword = keyword, start = start, end = end) |> + .query = list(keyword = keyword, start = start, end = end)) |> onet_perform() # Define expected schema @@ -83,15 +83,14 @@ onet_taxonomy_map <- function(code, from = c("active", "2010"), to = c("2010", " } # Build the endpoint path based on direction - endpoint <- if (from == "active") { - paste0("taxonomy/active/2010/", code) + if (from == "active") { + resp <- onet_request("taxonomy/active/2010", .path_segments = code) |> + onet_perform() } else { - paste0("taxonomy/2010/active/", code) + resp <- onet_request("taxonomy/2010/active", .path_segments = code) |> + onet_perform() } - resp <- onet_request(endpoint) |> - onet_perform() - # Define expected schema schema <- empty_tibble(code = character(), title = character()) diff --git a/R/database.R b/R/database.R index 80b3f4b..3b6c691 100644 --- a/R/database.R +++ b/R/database.R @@ -21,13 +21,14 @@ onet_tables <- function() { # Define expected schema schema <- empty_tibble(id = character(), title = character()) - if (is.null(resp$table) || length(resp$table) == 0) { + # The API returns tables as a top-level list, not nested under 'table' + if (!is.list(resp) || length(resp) == 0) { return(schema) } - map(resp$table, \(x) { + map(resp, \(x) { tibble( - id = x$id %||% NA_character_, + id = x$table_id %||% NA_character_, title = x$title %||% NA_character_ ) }) |> list_rbind() @@ -57,7 +58,7 @@ onet_table_info <- function(table_id) { cli_abort("{.arg table_id} must be a single character string.") } - resp <- onet_request("database/info", table_id) |> + resp <- onet_request("database/info", .path_segments = table_id) |> onet_perform() # Define expected schema @@ -136,7 +137,7 @@ onet_table <- function(table_id, page_size = 2000, show_progress = TRUE) { #' @return A list with `data` (tibble), `start`, `end`, and `total`. #' @keywords internal onet_table_page <- function(table_id, start = 1, end = 2000) { - resp <- onet_request("database/rows", table_id, start = start, end = end) |> + resp <- onet_request("database/rows", .path_segments = table_id, .query = list(start = start, end = end)) |> onet_perform() data <- if (is.null(resp$row) || length(resp$row) == 0) { diff --git a/R/occupations.R b/R/occupations.R index 1fa094b..e089a9a 100644 --- a/R/occupations.R +++ b/R/occupations.R @@ -18,7 +18,7 @@ #' head(occupations) #' } onet_occupations <- function(start = 1, end = 1000) { - resp <- onet_request("online/occupations", start = start, end = end) |> + resp <- onet_request("online/occupations", .query = list(start = start, end = end)) |> onet_perform() # Define expected schema @@ -51,7 +51,7 @@ onet_occupations <- function(start = 1, end = 1000) { #' } onet_occupation <- function(code) { validate_onet_code(code) - resp <- onet_request("online/occupations", code, "summary") |> + resp <- onet_request("online/occupations", .path_segments = c(code, "summary")) |> onet_perform() resp } @@ -71,7 +71,7 @@ onet_occupation <- function(code) { #' } onet_occupation_details <- function(code) { validate_onet_code(code) - resp <- onet_request("online/occupations", code, "details") |> + resp <- onet_request("online/occupations", .path_segments = c(code, "details")) |> onet_perform() resp } @@ -154,7 +154,7 @@ onet_abilities <- function(code) { #' } onet_technology <- function(code) { validate_onet_code(code) - resp <- onet_request("online/occupations", code, "hot_technology") |> + resp <- onet_request("online/occupations", .path_segments = c(code, "hot_technology")) |> onet_perform() # Define expected schema @@ -194,7 +194,7 @@ onet_occupation_element <- function(code, element) { validate_onet_code(code) # Get summary which contains all elements - resp <- onet_request("online/occupations", code, "summary") |> + resp <- onet_request("online/occupations", .path_segments = c(code, "summary")) |> onet_perform() # Define expected schema diff --git a/R/request.R b/R/request.R index 608deeb..98d48d3 100644 --- a/R/request.R +++ b/R/request.R @@ -6,14 +6,28 @@ onet_base_url <- "https://api-v2.onetcenter.org" #' Creates an httr2 request object configured for the O*NET API. #' #' @param .path Character string specifying the API endpoint path. -#' @param ... Additional path segments and query parameters passed to the API. +#' @param .path_segments Additional path segments to append to the URL path (optional). +#' @param .query Named list or arguments for query parameters (optional). #' #' @return An httr2 request object. #' @keywords internal -onet_request <- function(.path, ...) { - request(onet_base_url) |> - req_url_path_append(.path) |> - req_url_query(...) |> +onet_request <- function(.path, .path_segments = NULL, .query = list()) { + req <- request(onet_base_url) |> + req_url_path_append(.path) + + # Append additional path segments if provided + if (!is.null(.path_segments)) { + for (segment in .path_segments) { + req <- req |> req_url_path_append(segment) + } + } + + # Add query parameters if provided + if (length(.query) > 0) { + req <- req |> req_url_query(!!!.query) + } + + req |> req_headers(`X-API-Key` = onet_api_key()) |> req_retry( max_tries = 3, diff --git a/R/search.R b/R/search.R index 88da416..f02cc01 100644 --- a/R/search.R +++ b/R/search.R @@ -27,7 +27,7 @@ onet_search <- function(keyword, start = 1, end = 20) { cli_abort("{.arg keyword} must be a single character string.") } - resp <- onet_request("online/search", keyword = keyword, start = start, end = end) |> + resp <- onet_request("online/search", .query = list(keyword = keyword, start = start, end = end)) |> onet_perform() # Define expected schema for empty results @@ -44,10 +44,17 @@ onet_search <- function(keyword, start = 1, end = 20) { } results <- map(resp$occupation, \(x) { + # Try multiple field names for relevance score + relevance <- x$relevance_score %||% + x$relevanceScore %||% + x$relevance %||% + x$score %||% + NA_real_ + tibble( code = x$code %||% NA_character_, title = x$title %||% NA_character_, - relevance_score = x$relevance_score %||% NA_real_ + relevance_score = relevance ) }) |> list_rbind() diff --git a/tests/testthat/test-request-construction.R b/tests/testthat/test-request-construction.R new file mode 100644 index 0000000..3f7d083 --- /dev/null +++ b/tests/testthat/test-request-construction.R @@ -0,0 +1,68 @@ +test_that("onet_request builds URL correctly with path only", { + # This test verifies that onet_request can handle path-only endpoints + # without query parameters (which was causing errors before) + + # Mock the API key + old_key <- Sys.getenv("ONET_API_KEY") + on.exit(Sys.setenv(ONET_API_KEY = old_key)) + Sys.setenv(ONET_API_KEY = "test-key") + + # Build a request with only path + req <- onet2r:::onet_request("database") + + expect_s3_class(req, "httr2_request") + expect_match(req$url, "https://api-v2.onetcenter.org/database") +}) + +test_that("onet_request builds URL correctly with path segments", { + old_key <- Sys.getenv("ONET_API_KEY") + on.exit(Sys.setenv(ONET_API_KEY = old_key)) + Sys.setenv(ONET_API_KEY = "test-key") + + # Build a request with path segments + req <- onet2r:::onet_request("online/occupations", .path_segments = c("15-1252.00", "summary")) + + expect_s3_class(req, "httr2_request") + expect_match(req$url, "https://api-v2.onetcenter.org/online/occupations/15-1252.00/summary") +}) + +test_that("onet_request builds URL correctly with query parameters", { + old_key <- Sys.getenv("ONET_API_KEY") + on.exit(Sys.setenv(ONET_API_KEY = old_key)) + Sys.setenv(ONET_API_KEY = "test-key") + + # Build a request with query parameters + req <- onet2r:::onet_request("online/search", .query = list(keyword = "software", start = 1, end = 20)) + + expect_s3_class(req, "httr2_request") + expect_match(req$url, "https://api-v2.onetcenter.org/online/search") + # The query params should be in the URL + expect_match(req$url, "keyword=software") + expect_match(req$url, "start=1") + expect_match(req$url, "end=20") +}) + +test_that("onet_request builds URL correctly with both path segments and query parameters", { + old_key <- Sys.getenv("ONET_API_KEY") + on.exit(Sys.setenv(ONET_API_KEY = old_key)) + Sys.setenv(ONET_API_KEY = "test-key") + + # Build a request with both path segments and query parameters + req <- onet2r:::onet_request("database/rows", .path_segments = "skills", .query = list(start = 1, end = 100)) + + expect_s3_class(req, "httr2_request") + expect_match(req$url, "https://api-v2.onetcenter.org/database/rows/skills") + expect_match(req$url, "start=1") + expect_match(req$url, "end=100") +}) + +test_that("onet_request includes API key header", { + old_key <- Sys.getenv("ONET_API_KEY") + on.exit(Sys.setenv(ONET_API_KEY = old_key)) + test_key <- "test-api-key-123" + Sys.setenv(ONET_API_KEY = test_key) + + req <- onet2r:::onet_request("database") + + expect_equal(req$headers[["X-API-Key"]], test_key) +})