diff --git a/pkgdown.yml b/pkgdown.yml index c8892d7..dc159fa 100644 --- a/pkgdown.yml +++ b/pkgdown.yml @@ -2,7 +2,7 @@ pandoc: '3.2' pkgdown: 2.0.9 pkgdown_sha: ~ articles: {} -last_built: 2024-05-23T01:53Z +last_built: 2024-05-23T02:52Z urls: reference: https://hub.analythium.io/tryr/reference article: https://hub.analythium.io/tryr/articles diff --git a/reference/http-try.html b/reference/http-try.html index 5210512..5e60077 100644 --- a/reference/http-try.html +++ b/reference/http-try.html @@ -144,7 +144,7 @@

Examples#> [1] 500 http_try(req, res, { 2 + 2 }) -#> 6612 | 2024-05-23 01:53:30.108 [SUCCESS] Status 200: OK +#> 6713 | 2024-05-23 02:52:10.798 [SUCCESS] Status 200: OK #> [1] 4 res$status #> [1] 500 @@ -163,7 +163,7 @@

Examples#> [x] 401 http_try(req, res, http_success(201)) -#> 6612 | 2024-05-23 01:53:30.110 [SUCCESS] Status 201: Created +#> 6713 | 2024-05-23 02:52:10.799 [SUCCESS] Status 201: Created #> $category #> [x] "Successful" #> diff --git a/reference/msg.html b/reference/msg.html index 498e58f..f6981a6 100644 --- a/reference/msg.html +++ b/reference/msg.html @@ -134,21 +134,21 @@

Examples#> [1] "Sample size n = 5." msg("Success", "We did it!") -#> 6612 | 2024-05-23 01:53:30.588 [INFO ] Success - We did it! +#> 6713 | 2024-05-23 02:52:11.320 [INFO ] Success - We did it! msg("Success", "We did it!", "SUCCESS") -#> 6612 | 2024-05-23 01:53:30.588 [SUCCESS] Success - We did it! +#> 6713 | 2024-05-23 02:52:11.320 [SUCCESS] Success - We did it! msg("Error", "Oh no! n cannot be " %+% n, "ERROR") msg("Success", "We did it!", "SUCCESS", format = "JSON") -#> {"pid":"6612","ts":"2024-05-23 01:53:30.589","ut":1716429210.58977,"level":"SUCCESS","value":4,"title":"Success","message":"We did it!"} +#> {"pid":"6713","ts":"2024-05-23 02:52:11.321","ut":1716432731.32153,"level":"SUCCESS","value":4,"title":"Success","message":"We did it!"} msg("Success", "We did it!", format = "JSON") -#> {"pid":"6612","ts":"2024-05-23 01:53:30.590","ut":1716429210.59014,"level":"INFO","value":3,"title":"Success","message":"We did it!"} +#> {"pid":"6713","ts":"2024-05-23 02:52:11.321","ut":1716432731.32191,"level":"INFO","value":3,"title":"Success","message":"We did it!"} msg("Error", "Oh no ...", "ERROR", format = "JSON") msg("Success", "We did it!", digits = 0) -#> 6612 | 2024-05-23 01:53:30 [INFO ] Success - We did it! +#> 6713 | 2024-05-23 02:52:11 [INFO ] Success - We did it! msg("Success", "We did it!", digits = 6) -#> 6612 | 2024-05-23 01:53:30.591284 [INFO ] Success - We did it! +#> 6713 | 2024-05-23 02:52:11.323054 [INFO ] Success - We did it! diff --git a/search.json b/search.json index 19cf3d0..f70b865 100644 --- a/search.json +++ b/search.json @@ -1 +1 @@ -[{"path":"https://hub.analythium.io/tryr/authors.html","id":null,"dir":"","previous_headings":"","what":"Authors","title":"Authors and Citation","text":"Peter Solymos. Author, maintainer. Analythium Solutions Inc.. Copyright holder, funder.","code":""},{"path":"https://hub.analythium.io/tryr/authors.html","id":"citation","dir":"","previous_headings":"","what":"Citation","title":"Authors and Citation","text":"Solymos P (2024). tryr: Client/Server Error Handling HTTP API Frameworks. R package version 0.1.1, https://github.com/analythium/tryr.","code":"@Manual{, title = {tryr: Client/Server Error Handling for HTTP API Frameworks}, author = {Peter Solymos}, year = {2024}, note = {R package version 0.1.1}, url = {https://github.com/analythium/tryr}, }"},{"path":"https://hub.analythium.io/tryr/index.html","id":"tryr-clientserver-error-handling-for-http-apis","dir":"","previous_headings":"","what":"tryr: Client/Server Error Handling for HTTP APIs","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Differentiate client errors (4xx) server errors (5xx) simple built-logging mechanism Plumber RestRserve HTTP API frameworks. client/server setups, client might send user input incorrect. cases front end application needs know 4xx error status indicates message needs relayed user correct input. opposed , server fails due unexpected reasons, client needs know error 5xx status happened. Logs essential backend developers diagnose problem.","code":"remotes::install_github(\"analythium/tryr\")"},{"path":"https://hub.analythium.io/tryr/index.html","id":"problem-statement","dir":"","previous_headings":"","what":"Problem statement","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Plumber R package implements simple error catching hook converts responses error condition status code 500 - Internal Server Error. Let’s see example API:","code":"plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_run(port=8000)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"default-behavior","dir":"","previous_headings":"Problem statement","what":"Default behavior","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"use handler calling function foo(): note contents response also depend pr_set_debug() settings depends whether use interactive non-interactive session. now turned can see ‘production’ behavior. responses /test endpoint various specification x parameter: can see, response generic 500 HTTP status irrespective nature error. back end, error printed STDOUT, whereas warning got printed STDERR. default behavior undesired multiple reasons: need able differentiate 4xx 5xx errors detailed error message helpful backend, print STDERR instead STDOUT Warning: pr_set_debug(TRUE) error message returned response, might contain sensitive information leak client.","code":"foo <- function(x) { x <- as.numeric(x) if (x < 0) stop(\"'x' is too low.\") \"Success!\" } #* @post /test function(x) { foo(x = x) } # --- Request --- # curl -X POST \"http://localhost:8000/test?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/test?x=-1\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/test?x=a\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR --- # Warning in foo(x = x) : NAs introduced by coercion # --- Request --- # curl -X POST \"http://localhost:8000/test?x=\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR ---"},{"path":"https://hub.analythium.io/tryr/index.html","id":"trycatch-behavior","dir":"","previous_headings":"","what":"Try/catch behavior","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Alternatively, can use functions tryr handle inconveniences: outputs /try endpoint requests : Now can see : Successful response (200) leaves trace STDOUT can differentiate 4xx 5xx errors error 5xx, error message included detailed error message printed STDERR ? get tryr: used http_try(). wrapper can handle expected unexpected errors. Expected errors give desired HTTP statuses using http_error(). Unexpected error returned stop() little control (.e. written someone else).","code":"bar <- function(x) { x <- suppressWarnings(as.numeric(x)) if (is.na(x)) tryr::http_error(400L, \"Unexpected input.\") foo(x) } #* @post /try function(req, res, x) { tryr::http_try(req, res, { if (missing(x)) stop(\"'x' is missing\", call. = FALSE) bar(x = x) }) } # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # 10548 | 2024-05-21 11:51:24.084 [SUCCESS] Status 200: OK # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # # --- STDERR --- # 10580 | 2024-05-21 11:51:25.155 [ERROR ] Status 500: Internal Server Error - Error in foo(x) : 'x' is too low. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # # --- STDERR --- # 10592 | 2024-05-21 11:51:26.215 [ERROR ] Status 400: Bad Request - Unexpected input. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # # --- STDERR --- # 10624 | 2024-05-21 11:51:27.286 [ERROR ] Status 500: Internal Server Error - Error : 'x' is missing"},{"path":"https://hub.analythium.io/tryr/index.html","id":"implementation","dir":"","previous_headings":"","what":"Implementation","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"logic inside http_try() : catch error: log ERROR + print error message STDERR return generic status 500 message set status code response object 500 log ERROR message condition attribute return specific HTTP error code structured output set status code response object don’t catch error: log SUCCESS message element return specific HTTP status code structured output set status code response object log SUCCESS generic 200 message return object (default status code 200 assumed) Log messages handled msg function. can add preroute hook: add logger print incoming request info (HTTP method route) STDOUT. sake better ingesting logs can set logging type JSON (CSV) timestamp precision 6 digits. Output: Structured errors handled http_error() function uses default error messages defined http_status_codes data frame. http_success() works similarly produce error. can also pass body argument. useful need return simple status messages responding webhooks async execution. http_response() can used status codes behind http_handler() function useful set default handlers Plumber:","code":"Sys.setenv( TRYR_LOG_FORMAT = \"JSON\", TRYR_LOG_DIGITS = \"6\" ) plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_hooks( list( preroute = function(data, req, res) { tryr::msg( title = paste( method = req$REQUEST_METHOD, path = req$PATH_INFO ), level = \"INFO\" ) } ) ) |> plumber::pr_run( port = 8000, quiet = TRUE) # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # {\"pid\":\"10636\",\"ts\":\"2024-05-21 11:51:28.330135\",\"ut\":1716313888.33009,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # {\"pid\":\"10636\",\"ts\":\"2024-05-21 11:51:28.359236\",\"ut\":1716313888.35921,\"level\":\"SUCCESS\",\"value\":4,\"title\":\"Status 200: OK\",\"message\":\"\"} # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # {\"pid\":\"10668\",\"ts\":\"2024-05-21 11:51:29.40934\",\"ut\":1716313889.40931,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10668\",\"ts\":\"2024-05-21 11:51:29.435525\",\"ut\":1716313889.43551,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 500: Internal Server Error\",\"message\":\"Error in foo(x) : 'x' is too low.\"} # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # {\"pid\":\"10680\",\"ts\":\"2024-05-21 11:51:30.482933\",\"ut\":1716313890.48287,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10680\",\"ts\":\"2024-05-21 11:51:30.51150\",\"ut\":1716313890.51149,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 400: Bad Request - Unexpected input.\",\"message\":\"\"} # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # {\"pid\":\"10713\",\"ts\":\"2024-05-21 11:51:31.556088\",\"ut\":1716313891.55603,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10713\",\"ts\":\"2024-05-21 11:51:31.584738\",\"ut\":1716313891.58471,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 500: Internal Server Error\",\"message\":\"Error : 'x' is missing\"} plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_set_404( function(req, res) { tryr::msg( title = paste0( \"Status 404: \", tryr::http_status_codes[\"404\", \"message\"]), level = \"INFO\" ) tryr::http_handler(req, res, 404L) } ) |> plumber::pr_set_error( function(req, res, err) { tryr::msg( title = paste0( \"Status 500: \", tryr::http_status_codes[\"500\", \"message\"]), message = err, level = \"ERROR\" ) tryr::http_handler(req, res, 500L) } ) |> plumber::pr_hooks( list( preroute = function(data, req, res) { tryr::msg( title = paste( method = req$REQUEST_METHOD, path = req$PATH_INFO ), level = \"INFO\" ) } ) ) |> plumber::pr_run( port = 8000, quiet = TRUE)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"see-the-action","dir":"","previous_headings":"","what":"See the action","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"inst/examples folder contains Shiny apps can edit use explore differences Plumber’s default error handling (/test endpoint) tryr approach (/tryr endpoint). See response, request, STDOUT STDERR calls: second app general. can edit plumber_fun function definition explore API’s output printed STDOUT STDERR:","code":"source(\"inst/examples/app.R\") source(\"inst/examples/explore.R\")"},{"path":"https://hub.analythium.io/tryr/index.html","id":"supported-api-frameworks","dir":"","previous_headings":"","what":"Supported API frameworks","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"plumber: popular API framework R accounting 95% total downloads. RestRserve: second popular framework accounting 2% total downloads. See RestRserve.R example. frameworks (fiery, beakr, ambiorix) supported – using likely result error. Let’s see example RestRserve:","code":"## An example RestRserve API from https://restrserve.org/ library(RestRserve) app = Application$new() app$logger$set_log_level(\"off\") # using tryr's logger foo <- function(x) { x <- as.numeric(x) if (x < 0) stop(\"'x' is too low.\") \"Success!\" } bar <- function(x) { x <- suppressWarnings(as.numeric(x)) if (is.na(x)) tryr::http_error(400L, \"Unexpected input.\") foo(x) } app$add_post( path = \"/test\", FUN = function(req, res) { x <- req$parameters_query[[\"x\"]] out <- foo(x = x) res$set_content_type(\"application/json\") res$set_body(out) }) app$add_post( path = \"/try\", FUN = function(req, res) { out <- tryr::http_try(req, res, { x <- req$parameters_query[[\"x\"]] if (is.null(x)) stop(\"'x' is missing\", call. = FALSE) bar(x = x) }) res$set_content_type(\"application/json\") res$set_body(out) }) backend = BackendRserve$new() backend$start(app, http_port = 8000) # Rscript inst/examples/RestRserve.R # curl -i -X POST \"http://localhost:8000/try?x=0\" # curl -i -X POST \"http://localhost:8000/try?x=-1\" # curl -i -X POST \"http://localhost:8000/try?x=a\" # curl -i -X POST \"http://localhost:8000/try?x=\" # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # \"Success!\" # --- STDOUT --- # -- running Rserve in this R session (pid=10727), 2 server(s) -- # (This session will block until Rserve is shut down) # 10759 | 2024-05-21 11:51:32.638 [SUCCESS] Status 200: OK # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10760), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10772 | 2024-05-21 11:51:33.681 [ERROR ] Status 500: Internal Server Error - Error in foo(x) : 'x' is too low. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10773), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10805 | 2024-05-21 11:51:34.723 [ERROR ] Status 400: Bad Request - Unexpected input. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10806), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10818 | 2024-05-21 11:51:35.766 [ERROR ] Status 500: Internal Server Error - Error : 'x' is missing"},{"path":"https://hub.analythium.io/tryr/index.html","id":"what-else-is-included","dir":"","previous_headings":"","what":"What else is included","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"functions use msg function logging. function writes log message STDOUT STDERR side effect. Instead using glue, sprintf, paste, can use %+% operator similar addition JavaScript: can’t see errors written STDERR.","code":"library(tryr) n <- 5 \"n = \" %+% n # [1] \"n = 5\" msg(\"Success\", \"We did it!\") # 10442 | 2024-05-21 11:51:35.782 [INFO ] Success - We did it! msg(\"Success\", \"n = \" %+% n %+% \" is right\", \"SUCCESS\") # 10442 | 2024-05-21 11:51:35.783 [SUCCESS] Success - n = 5 is right msg(\"Error\", \"n = \" %+% n %+% \" is too high\", \"ERROR\") msg(\"Success\", \"We did it!\", format = \"JSON\") # {\"pid\":\"10442\",\"ts\":\"2024-05-21 11:51:35.784\",\"ut\":1716313895.78436,\"level\":\"INFO\",\"value\":3,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Success\", \"n = \" %+% n %+% \" is right\", \"SUCCESS\", digits = 0) # 10442 | 2024-05-21 11:51:35 [SUCCESS] Success - n = 5 is right msg(\"Error\", \"n = \" %+% n %+% \" is too high\", \"ERROR\", digits = 6)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"other-considerations","dir":"","previous_headings":"","what":"Other considerations","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Similar ideas tryCatchLog package general use case. STDOUT buffered, needs flush. STDERR unbuffered, immediate (progress reports/logging information belong stderr stdout?)","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":null,"dir":"Reference","previous_headings":"","what":"Generic HTTP Response Messages — http-messages","title":"Generic HTTP Response Messages — http-messages","text":"functions provide generic HTTP response messages based HTTP response status codes.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generic HTTP Response Messages — http-messages","text":"","code":"http_error(status = 500L, message = NULL) http_success(status = 200L, message = NULL, body = NULL) http_response(status = 200L, message = NULL, body = NULL) http_handler(req, res, status = 200L, message = NULL, body = NULL)"},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Generic HTTP Response Messages — http-messages","text":"status HTTP status code. message HTTP response message NULL. generic response message provided NULL based http_status_codes. body list, additional values returned. req request object. res response object.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Generic HTTP Response Messages — http-messages","text":"http_error returns error custom condition attribute checking status code least 400. http_success returns list checks status code <400. http_response returns list checking status code valid. http_handler behaves like http_response also sets status code body response object.","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Generic HTTP Response Messages — http-messages","text":"","code":"try(http_error()) #> Error : Internal Server Error try(http_error(400)) #> Error : Bad Request try(http_error(400, \"Sorry\")) #> Error : Bad Request - Sorry str(http_success()) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 200 #> $ message : 'scalar' chr \"OK\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_success(201)) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_success(201, \"Awesome\")) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_response(201, \"Awesome\", list(name = \"Jane\", count = 6))) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\" req <- new.env() res <- new.env() str(http_handler(req, res, 201, \"Awesome\", list(name = \"Jane\", count = 6))) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\" res$status #> [1] 201 str(res$body) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":null,"dir":"Reference","previous_headings":"","what":"Client/Server Error Handling — http-try","title":"Client/Server Error Handling — http-try","text":"Differentiate client (4xx) server (5xx) errors. Provides mechanism return custom status codes combination http_error() http_success().","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Client/Server Error Handling — http-try","text":"","code":"http_try(req, res, expr, silent = TRUE, ...) http_try_handler(req, res, x)"},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Client/Server Error Handling — http-try","text":"req request object. res response object. expr R expression try. silent Logical, report error messages suppressed try()? ... Arguments passed try() x return value try(expr).","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Client/Server Error Handling — http-try","text":"list results expr. side effect setting response status code response object log message STDOUT STDERR.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Client/Server Error Handling — http-try","text":"catch error: error clear server error coming stop() log ERROR + print error message STDERR return generic status 500 message set status code response object 500 error structured HTTP error coming http_error() log ERROR message condition attribute return specific HTTP error code structured output set status code response object catch error: object class http_success() (comes handy async jobs redirects) log SUCCESS message element return specific HTTP status code structured output set status code response object object class http_success() log SUCCESS generic 200 message return object (default status code 200 assumed)","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Client/Server Error Handling — http-try","text":"","code":"req <- new.env() res <- new.env() http_try(req, res) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try(req, res, { 2 + 2 }) #> 6612 | 2024-05-23 01:53:30.108 [SUCCESS] Status 200: OK #> [1] 4 res$status #> [1] 500 http_try(req, res, http_error(401)) #> $category #> [x] \"Client Error\" #> #> $status #> [x] 401 #> #> $message #> [x] \"Unauthorized\" #> res$status #> [x] 401 http_try(req, res, http_success(201)) #> 6612 | 2024-05-23 01:53:30.110 [SUCCESS] Status 201: Created #> $category #> [x] \"Successful\" #> #> $status #> [x] 201 #> #> $message #> [x] \"Created\" #> res$status #> [x] 201 http_try(req, res, { lm(NULL) }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try(req, res, { stop(\"Stop!!!\") }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 f <- function() stop(\"Stop!!!\") http_try(req, res, { f() }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try_handler(req, res, { try(f()) }) #> Error in f() : Stop!!! #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500"},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":null,"dir":"Reference","previous_headings":"","what":"HTTP Response Status Codes — http_status_codes","title":"HTTP Response Status Codes — http_status_codes","text":"Data frame possible status codes default messages based RFC 9110. See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Status.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"HTTP Response Status Codes — http_status_codes","text":"","code":"http_status_codes"},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"format","dir":"Reference","previous_headings":"","what":"Format","title":"HTTP Response Status Codes — http_status_codes","text":"object class data.frame 62 rows 3 columns.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"HTTP Response Status Codes — http_status_codes","text":"","code":"str(http_status_codes) #> 'data.frame':\t62 obs. of 3 variables: #> $ category: chr \"Informational\" \"Informational\" \"Informational\" \"Informational\" ... #> $ status : int 100 101 102 103 200 201 202 203 204 205 ... #> $ message : chr \"Continue\" \"Switching Protocols\" \"Processing\" \"Early Hints\" ... http_status_codes[http_status_codes$category == \"Successful\",] #> category status message #> 200 Successful 200 OK #> 201 Successful 201 Created #> 202 Successful 202 Accepted #> 203 Successful 203 Non-Authoritative Information #> 204 Successful 204 No Content #> 205 Successful 205 Reset Content #> 206 Successful 206 Partial Content #> 207 Successful 207 Multi-Status #> 208 Successful 208 Already Reported #> 226 Successful 226 IM Used http_status_codes[\"500\",] #> category status message #> 500 Server Error 500 Internal Server Error"},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":null,"dir":"Reference","previous_headings":"","what":"Write Logging Information to STDOUT or STDERR — msg","title":"Write Logging Information to STDOUT or STDERR — msg","text":"simple logging utility writes log message STDOUT STDERR plain text, JSON, comma separated format.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Write Logging Information to STDOUT or STDERR — msg","text":"","code":"msg(title = \"\", message = \"\", level = \"INFO\", format = NULL, digits = NULL) x %+% y"},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Write Logging Information to STDOUT or STDERR — msg","text":"title Character, title logging message. message Character, detailed logging message. level Log level; one \"\", \"TRACE\", \"DEBUG\", \"INFO\", \"SUCCESS\", \"WARN\", \"ERROR\", \"FATAL\", \"\" (case insensitive). format Log format: \"PLAIN\" (default), \"JSON\", \"CSV\" (case insensitive). digits Integer length 1, digits seconds (default 3L meaning milliseconds). x, y Strings combine.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Write Logging Information to STDOUT or STDERR — msg","text":"msg invisibly returns logical indicating log message written (TRUE) (FALSE). side effect log message STDOUT STDERR. %+% special pastes right left hand side together s single string.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Write Logging Information to STDOUT or STDERR — msg","text":"TRYR_ERR_LEVEL environment variable determines log message written. log level least one TRYR_ERR_LEVEL value (\"WARN\" unset null) message written STDERR, otherwise written STDOUT. log message written log level least one higher specified TRYR_LOG_LEVEL environment variables; \"INFO\" variable unset null. log format can plain text, JSON, comma separated text. log format NULL TRYR_LOG_FORMAT environment variables checked. unset null, format considered plain text. logging message formed combining title message parts. log info also contains process ID, timestamp (using Sys.time()), log level. timestamp prints fractional seconds according digits. digits NULL checks TRYR_LOG_DIGITS environment variables uses value. default 3 TRYR_LOG_DIGITS unset null. Besides usual log levels, extra one \"SUCCESS\" used signal successful HTTP response codes (2xx).","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Write Logging Information to STDOUT or STDERR — msg","text":"","code":"n <- 5 \"Sample \" %+% \"size \" %+% \"n = \" %+% n %+% \".\" #> [1] \"Sample size n = 5.\" msg(\"Success\", \"We did it!\") #> 6612 | 2024-05-23 01:53:30.588 [INFO ] Success - We did it! msg(\"Success\", \"We did it!\", \"SUCCESS\") #> 6612 | 2024-05-23 01:53:30.588 [SUCCESS] Success - We did it! msg(\"Error\", \"Oh no! n cannot be \" %+% n, \"ERROR\") msg(\"Success\", \"We did it!\", \"SUCCESS\", format = \"JSON\") #> {\"pid\":\"6612\",\"ts\":\"2024-05-23 01:53:30.589\",\"ut\":1716429210.58977,\"level\":\"SUCCESS\",\"value\":4,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Success\", \"We did it!\", format = \"JSON\") #> {\"pid\":\"6612\",\"ts\":\"2024-05-23 01:53:30.590\",\"ut\":1716429210.59014,\"level\":\"INFO\",\"value\":3,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Error\", \"Oh no ...\", \"ERROR\", format = \"JSON\") msg(\"Success\", \"We did it!\", digits = 0) #> 6612 | 2024-05-23 01:53:30 [INFO ] Success - We did it! msg(\"Success\", \"We did it!\", digits = 6) #> 6612 | 2024-05-23 01:53:30.591284 [INFO ] Success - We did it!"},{"path":"https://hub.analythium.io/tryr/news/index.html","id":"version-011","dir":"Changelog","previous_headings":"","what":"Version 0.1.1","title":"Version 0.1.1","text":"Single quoting R packages DESCRIPTION.","code":""},{"path":"https://hub.analythium.io/tryr/news/index.html","id":"version-010","dir":"Changelog","previous_headings":"","what":"Version 0.1.0","title":"Version 0.1.0","text":"First stable release failed CRAN submission.","code":""}] +[{"path":"https://hub.analythium.io/tryr/authors.html","id":null,"dir":"","previous_headings":"","what":"Authors","title":"Authors and Citation","text":"Peter Solymos. Author, maintainer. Analythium Solutions Inc.. Copyright holder, funder.","code":""},{"path":"https://hub.analythium.io/tryr/authors.html","id":"citation","dir":"","previous_headings":"","what":"Citation","title":"Authors and Citation","text":"Solymos P (2024). tryr: Client/Server Error Handling HTTP API Frameworks. R package version 0.1.1, https://github.com/analythium/tryr.","code":"@Manual{, title = {tryr: Client/Server Error Handling for HTTP API Frameworks}, author = {Peter Solymos}, year = {2024}, note = {R package version 0.1.1}, url = {https://github.com/analythium/tryr}, }"},{"path":"https://hub.analythium.io/tryr/index.html","id":"tryr-clientserver-error-handling-for-http-apis","dir":"","previous_headings":"","what":"tryr: Client/Server Error Handling for HTTP APIs","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Differentiate client errors (4xx) server errors (5xx) simple built-logging mechanism Plumber RestRserve HTTP API frameworks. client/server setups, client might send user input incorrect. cases front end application needs know 4xx error status indicates message needs relayed user correct input. opposed , server fails due unexpected reasons, client needs know error 5xx status happened. Logs essential backend developers diagnose problem.","code":"remotes::install_github(\"analythium/tryr\")"},{"path":"https://hub.analythium.io/tryr/index.html","id":"problem-statement","dir":"","previous_headings":"","what":"Problem statement","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Plumber R package implements simple error catching hook converts responses error condition status code 500 - Internal Server Error. Let’s see example API:","code":"plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_run(port=8000)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"default-behavior","dir":"","previous_headings":"Problem statement","what":"Default behavior","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"use handler calling function foo(): note contents response also depend pr_set_debug() settings depends whether use interactive non-interactive session. now turned can see ‘production’ behavior. responses /test endpoint various specification x parameter: can see, response generic 500 HTTP status irrespective nature error. back end, error printed STDOUT, whereas warning got printed STDERR. default behavior undesired multiple reasons: need able differentiate 4xx 5xx errors detailed error message helpful backend, print STDERR instead STDOUT Warning: pr_set_debug(TRUE) error message returned response, might contain sensitive information leak client.","code":"foo <- function(x) { x <- as.numeric(x) if (x < 0) stop(\"'x' is too low.\") \"Success!\" } #* @post /test function(x) { foo(x = x) } # --- Request --- # curl -X POST \"http://localhost:8000/test?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/test?x=-1\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/test?x=a\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR --- # Warning in foo(x = x) : NAs introduced by coercion # --- Request --- # curl -X POST \"http://localhost:8000/test?x=\" # --- Response --- # {\"error\":\"500 - Internal server error\"} # --- STDOUT --- # # --- STDERR ---"},{"path":"https://hub.analythium.io/tryr/index.html","id":"trycatch-behavior","dir":"","previous_headings":"","what":"Try/catch behavior","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Alternatively, can use functions tryr handle inconveniences: outputs /try endpoint requests : Now can see : Successful response (200) leaves trace STDOUT can differentiate 4xx 5xx errors error 5xx, error message included detailed error message printed STDERR ? get tryr: used http_try(). wrapper can handle expected unexpected errors. Expected errors give desired HTTP statuses using http_error(). Unexpected error returned stop() little control (.e. written someone else).","code":"bar <- function(x) { x <- suppressWarnings(as.numeric(x)) if (is.na(x)) tryr::http_error(400L, \"Unexpected input.\") foo(x) } #* @post /try function(req, res, x) { tryr::http_try(req, res, { if (missing(x)) stop(\"'x' is missing\", call. = FALSE) bar(x = x) }) } # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # 10548 | 2024-05-21 11:51:24.084 [SUCCESS] Status 200: OK # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # # --- STDERR --- # 10580 | 2024-05-21 11:51:25.155 [ERROR ] Status 500: Internal Server Error - Error in foo(x) : 'x' is too low. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # # --- STDERR --- # 10592 | 2024-05-21 11:51:26.215 [ERROR ] Status 400: Bad Request - Unexpected input. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # # --- STDERR --- # 10624 | 2024-05-21 11:51:27.286 [ERROR ] Status 500: Internal Server Error - Error : 'x' is missing"},{"path":"https://hub.analythium.io/tryr/index.html","id":"implementation","dir":"","previous_headings":"","what":"Implementation","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"logic inside http_try() : catch error: log ERROR + print error message STDERR return generic status 500 message set status code response object 500 log ERROR message condition attribute return specific HTTP error code structured output set status code response object don’t catch error: log SUCCESS message element return specific HTTP status code structured output set status code response object log SUCCESS generic 200 message return object (default status code 200 assumed) Log messages handled msg function. can add preroute hook: add logger print incoming request info (HTTP method route) STDOUT. sake better ingesting logs can set logging type JSON (CSV) timestamp precision 6 digits. Output: Structured errors handled http_error() function uses default error messages defined http_status_codes data frame. http_success() works similarly produce error. can also pass body argument. useful need return simple status messages responding webhooks async execution. http_response() can used status codes behind http_handler() function useful set default handlers Plumber:","code":"Sys.setenv( TRYR_LOG_FORMAT = \"JSON\", TRYR_LOG_DIGITS = \"6\" ) plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_hooks( list( preroute = function(data, req, res) { tryr::msg( title = paste( method = req$REQUEST_METHOD, path = req$PATH_INFO ), level = \"INFO\" ) } ) ) |> plumber::pr_run( port = 8000, quiet = TRUE) # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # [\"Success!\"] # --- STDOUT --- # {\"pid\":\"10636\",\"ts\":\"2024-05-21 11:51:28.330135\",\"ut\":1716313888.33009,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # {\"pid\":\"10636\",\"ts\":\"2024-05-21 11:51:28.359236\",\"ut\":1716313888.35921,\"level\":\"SUCCESS\",\"value\":4,\"title\":\"Status 200: OK\",\"message\":\"\"} # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # {\"pid\":\"10668\",\"ts\":\"2024-05-21 11:51:29.40934\",\"ut\":1716313889.40931,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10668\",\"ts\":\"2024-05-21 11:51:29.435525\",\"ut\":1716313889.43551,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 500: Internal Server Error\",\"message\":\"Error in foo(x) : 'x' is too low.\"} # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # {\"pid\":\"10680\",\"ts\":\"2024-05-21 11:51:30.482933\",\"ut\":1716313890.48287,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10680\",\"ts\":\"2024-05-21 11:51:30.51150\",\"ut\":1716313890.51149,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 400: Bad Request - Unexpected input.\",\"message\":\"\"} # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # {\"pid\":\"10713\",\"ts\":\"2024-05-21 11:51:31.556088\",\"ut\":1716313891.55603,\"level\":\"INFO\",\"value\":3,\"title\":\"POST /try\",\"message\":\"\"} # --- STDERR --- # {\"pid\":\"10713\",\"ts\":\"2024-05-21 11:51:31.584738\",\"ut\":1716313891.58471,\"level\":\"ERROR\",\"value\":6,\"title\":\"Status 500: Internal Server Error\",\"message\":\"Error : 'x' is missing\"} plumber::pr(\"inst/examples/plumber.R\") |> plumber::pr_set_debug(FALSE) |> plumber::pr_set_404( function(req, res) { tryr::msg( title = paste0( \"Status 404: \", tryr::http_status_codes[\"404\", \"message\"]), level = \"INFO\" ) tryr::http_handler(req, res, 404L) } ) |> plumber::pr_set_error( function(req, res, err) { tryr::msg( title = paste0( \"Status 500: \", tryr::http_status_codes[\"500\", \"message\"]), message = err, level = \"ERROR\" ) tryr::http_handler(req, res, 500L) } ) |> plumber::pr_hooks( list( preroute = function(data, req, res) { tryr::msg( title = paste( method = req$REQUEST_METHOD, path = req$PATH_INFO ), level = \"INFO\" ) } ) ) |> plumber::pr_run( port = 8000, quiet = TRUE)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"see-the-action","dir":"","previous_headings":"","what":"See the action","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"inst/examples folder contains Shiny apps can edit use explore differences Plumber’s default error handling (/test endpoint) tryr approach (/tryr endpoint). See response, request, STDOUT STDERR calls: second app general. can edit plumber_fun function definition explore API’s output printed STDOUT STDERR:","code":"source(\"inst/examples/app.R\") source(\"inst/examples/explore.R\")"},{"path":"https://hub.analythium.io/tryr/index.html","id":"supported-api-frameworks","dir":"","previous_headings":"","what":"Supported API frameworks","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"plumber: popular API framework R accounting 95% total downloads. RestRserve: second popular framework accounting 2% total downloads. See RestRserve.R example. frameworks (fiery, beakr, ambiorix) supported – using likely result error. Let’s see example RestRserve:","code":"## An example RestRserve API from https://restrserve.org/ library(RestRserve) app = Application$new() app$logger$set_log_level(\"off\") # using tryr's logger foo <- function(x) { x <- as.numeric(x) if (x < 0) stop(\"'x' is too low.\") \"Success!\" } bar <- function(x) { x <- suppressWarnings(as.numeric(x)) if (is.na(x)) tryr::http_error(400L, \"Unexpected input.\") foo(x) } app$add_post( path = \"/test\", FUN = function(req, res) { x <- req$parameters_query[[\"x\"]] out <- foo(x = x) res$set_content_type(\"application/json\") res$set_body(out) }) app$add_post( path = \"/try\", FUN = function(req, res) { out <- tryr::http_try(req, res, { x <- req$parameters_query[[\"x\"]] if (is.null(x)) stop(\"'x' is missing\", call. = FALSE) bar(x = x) }) res$set_content_type(\"application/json\") res$set_body(out) }) backend = BackendRserve$new() backend$start(app, http_port = 8000) # Rscript inst/examples/RestRserve.R # curl -i -X POST \"http://localhost:8000/try?x=0\" # curl -i -X POST \"http://localhost:8000/try?x=-1\" # curl -i -X POST \"http://localhost:8000/try?x=a\" # curl -i -X POST \"http://localhost:8000/try?x=\" # --- Request --- # curl -X POST \"http://localhost:8000/try?x=0\" # --- Response --- # \"Success!\" # --- STDOUT --- # -- running Rserve in this R session (pid=10727), 2 server(s) -- # (This session will block until Rserve is shut down) # 10759 | 2024-05-21 11:51:32.638 [SUCCESS] Status 200: OK # --- STDERR --- # --- Request --- # curl -X POST \"http://localhost:8000/try?x=-1\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10760), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10772 | 2024-05-21 11:51:33.681 [ERROR ] Status 500: Internal Server Error - Error in foo(x) : 'x' is too low. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=a\" # --- Response --- # {\"category\":\"Client Error\",\"status\":400,\"message\":\"Bad Request - Unexpected input.\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10773), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10805 | 2024-05-21 11:51:34.723 [ERROR ] Status 400: Bad Request - Unexpected input. # --- Request --- # curl -X POST \"http://localhost:8000/try?x=\" # --- Response --- # {\"category\":\"Server Error\",\"status\":500,\"message\":\"Internal Server Error\"} # --- STDOUT --- # -- running Rserve in this R session (pid=10806), 2 server(s) -- # (This session will block until Rserve is shut down) # --- STDERR --- # 10818 | 2024-05-21 11:51:35.766 [ERROR ] Status 500: Internal Server Error - Error : 'x' is missing"},{"path":"https://hub.analythium.io/tryr/index.html","id":"what-else-is-included","dir":"","previous_headings":"","what":"What else is included","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"functions use msg function logging. function writes log message STDOUT STDERR side effect. Instead using glue, sprintf, paste, can use %+% operator similar addition JavaScript: can’t see errors written STDERR.","code":"library(tryr) n <- 5 \"n = \" %+% n # [1] \"n = 5\" msg(\"Success\", \"We did it!\") # 10442 | 2024-05-21 11:51:35.782 [INFO ] Success - We did it! msg(\"Success\", \"n = \" %+% n %+% \" is right\", \"SUCCESS\") # 10442 | 2024-05-21 11:51:35.783 [SUCCESS] Success - n = 5 is right msg(\"Error\", \"n = \" %+% n %+% \" is too high\", \"ERROR\") msg(\"Success\", \"We did it!\", format = \"JSON\") # {\"pid\":\"10442\",\"ts\":\"2024-05-21 11:51:35.784\",\"ut\":1716313895.78436,\"level\":\"INFO\",\"value\":3,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Success\", \"n = \" %+% n %+% \" is right\", \"SUCCESS\", digits = 0) # 10442 | 2024-05-21 11:51:35 [SUCCESS] Success - n = 5 is right msg(\"Error\", \"n = \" %+% n %+% \" is too high\", \"ERROR\", digits = 6)"},{"path":"https://hub.analythium.io/tryr/index.html","id":"other-considerations","dir":"","previous_headings":"","what":"Other considerations","title":"tryr: Client/Server Error Handling for HTTP APIs","text":"Similar ideas tryCatchLog package general use case. STDOUT buffered, needs flush. STDERR unbuffered, immediate (progress reports/logging information belong stderr stdout?)","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":null,"dir":"Reference","previous_headings":"","what":"Generic HTTP Response Messages — http-messages","title":"Generic HTTP Response Messages — http-messages","text":"functions provide generic HTTP response messages based HTTP response status codes.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Generic HTTP Response Messages — http-messages","text":"","code":"http_error(status = 500L, message = NULL) http_success(status = 200L, message = NULL, body = NULL) http_response(status = 200L, message = NULL, body = NULL) http_handler(req, res, status = 200L, message = NULL, body = NULL)"},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Generic HTTP Response Messages — http-messages","text":"status HTTP status code. message HTTP response message NULL. generic response message provided NULL based http_status_codes. body list, additional values returned. req request object. res response object.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Generic HTTP Response Messages — http-messages","text":"http_error returns error custom condition attribute checking status code least 400. http_success returns list checks status code <400. http_response returns list checking status code valid. http_handler behaves like http_response also sets status code body response object.","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/http-messages.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Generic HTTP Response Messages — http-messages","text":"","code":"try(http_error()) #> Error : Internal Server Error try(http_error(400)) #> Error : Bad Request try(http_error(400, \"Sorry\")) #> Error : Bad Request - Sorry str(http_success()) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 200 #> $ message : 'scalar' chr \"OK\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_success(201)) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_success(201, \"Awesome\")) #> List of 3 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> - attr(*, \"class\")= chr [1:2] \"http_success\" \"http_response\" str(http_response(201, \"Awesome\", list(name = \"Jane\", count = 6))) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\" req <- new.env() res <- new.env() str(http_handler(req, res, 201, \"Awesome\", list(name = \"Jane\", count = 6))) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\" res$status #> [1] 201 str(res$body) #> List of 4 #> $ category: 'scalar' chr \"Successful\" #> $ status : 'scalar' int 201 #> $ message : 'scalar' chr \"Created - Awesome\" #> $ body :List of 2 #> ..$ name : chr \"Jane\" #> ..$ count: num 6 #> - attr(*, \"class\")= chr [1:2] \"http_response\" \"list\""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":null,"dir":"Reference","previous_headings":"","what":"Client/Server Error Handling — http-try","title":"Client/Server Error Handling — http-try","text":"Differentiate client (4xx) server (5xx) errors. Provides mechanism return custom status codes combination http_error() http_success().","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Client/Server Error Handling — http-try","text":"","code":"http_try(req, res, expr, silent = TRUE, ...) http_try_handler(req, res, x)"},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Client/Server Error Handling — http-try","text":"req request object. res response object. expr R expression try. silent Logical, report error messages suppressed try()? ... Arguments passed try() x return value try(expr).","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Client/Server Error Handling — http-try","text":"list results expr. side effect setting response status code response object log message STDOUT STDERR.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Client/Server Error Handling — http-try","text":"catch error: error clear server error coming stop() log ERROR + print error message STDERR return generic status 500 message set status code response object 500 error structured HTTP error coming http_error() log ERROR message condition attribute return specific HTTP error code structured output set status code response object catch error: object class http_success() (comes handy async jobs redirects) log SUCCESS message element return specific HTTP status code structured output set status code response object object class http_success() log SUCCESS generic 200 message return object (default status code 200 assumed)","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/http-try.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Client/Server Error Handling — http-try","text":"","code":"req <- new.env() res <- new.env() http_try(req, res) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try(req, res, { 2 + 2 }) #> 6713 | 2024-05-23 02:52:10.798 [SUCCESS] Status 200: OK #> [1] 4 res$status #> [1] 500 http_try(req, res, http_error(401)) #> $category #> [x] \"Client Error\" #> #> $status #> [x] 401 #> #> $message #> [x] \"Unauthorized\" #> res$status #> [x] 401 http_try(req, res, http_success(201)) #> 6713 | 2024-05-23 02:52:10.799 [SUCCESS] Status 201: Created #> $category #> [x] \"Successful\" #> #> $status #> [x] 201 #> #> $message #> [x] \"Created\" #> res$status #> [x] 201 http_try(req, res, { lm(NULL) }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try(req, res, { stop(\"Stop!!!\") }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 f <- function() stop(\"Stop!!!\") http_try(req, res, { f() }) #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500 http_try_handler(req, res, { try(f()) }) #> Error in f() : Stop!!! #> $category #> [x] \"Server Error\" #> #> $status #> [x] 500 #> #> $message #> [x] \"Internal Server Error\" #> res$status #> [1] 500"},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":null,"dir":"Reference","previous_headings":"","what":"HTTP Response Status Codes — http_status_codes","title":"HTTP Response Status Codes — http_status_codes","text":"Data frame possible status codes default messages based RFC 9110. See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Status.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"HTTP Response Status Codes — http_status_codes","text":"","code":"http_status_codes"},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"format","dir":"Reference","previous_headings":"","what":"Format","title":"HTTP Response Status Codes — http_status_codes","text":"object class data.frame 62 rows 3 columns.","code":""},{"path":"https://hub.analythium.io/tryr/reference/http_status_codes.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"HTTP Response Status Codes — http_status_codes","text":"","code":"str(http_status_codes) #> 'data.frame':\t62 obs. of 3 variables: #> $ category: chr \"Informational\" \"Informational\" \"Informational\" \"Informational\" ... #> $ status : int 100 101 102 103 200 201 202 203 204 205 ... #> $ message : chr \"Continue\" \"Switching Protocols\" \"Processing\" \"Early Hints\" ... http_status_codes[http_status_codes$category == \"Successful\",] #> category status message #> 200 Successful 200 OK #> 201 Successful 201 Created #> 202 Successful 202 Accepted #> 203 Successful 203 Non-Authoritative Information #> 204 Successful 204 No Content #> 205 Successful 205 Reset Content #> 206 Successful 206 Partial Content #> 207 Successful 207 Multi-Status #> 208 Successful 208 Already Reported #> 226 Successful 226 IM Used http_status_codes[\"500\",] #> category status message #> 500 Server Error 500 Internal Server Error"},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":null,"dir":"Reference","previous_headings":"","what":"Write Logging Information to STDOUT or STDERR — msg","title":"Write Logging Information to STDOUT or STDERR — msg","text":"simple logging utility writes log message STDOUT STDERR plain text, JSON, comma separated format.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"ref-usage","dir":"Reference","previous_headings":"","what":"Usage","title":"Write Logging Information to STDOUT or STDERR — msg","text":"","code":"msg(title = \"\", message = \"\", level = \"INFO\", format = NULL, digits = NULL) x %+% y"},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"arguments","dir":"Reference","previous_headings":"","what":"Arguments","title":"Write Logging Information to STDOUT or STDERR — msg","text":"title Character, title logging message. message Character, detailed logging message. level Log level; one \"\", \"TRACE\", \"DEBUG\", \"INFO\", \"SUCCESS\", \"WARN\", \"ERROR\", \"FATAL\", \"\" (case insensitive). format Log format: \"PLAIN\" (default), \"JSON\", \"CSV\" (case insensitive). digits Integer length 1, digits seconds (default 3L meaning milliseconds). x, y Strings combine.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"value","dir":"Reference","previous_headings":"","what":"Value","title":"Write Logging Information to STDOUT or STDERR — msg","text":"msg invisibly returns logical indicating log message written (TRUE) (FALSE). side effect log message STDOUT STDERR. %+% special pastes right left hand side together s single string.","code":""},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"details","dir":"Reference","previous_headings":"","what":"Details","title":"Write Logging Information to STDOUT or STDERR — msg","text":"TRYR_ERR_LEVEL environment variable determines log message written. log level least one TRYR_ERR_LEVEL value (\"WARN\" unset null) message written STDERR, otherwise written STDOUT. log message written log level least one higher specified TRYR_LOG_LEVEL environment variables; \"INFO\" variable unset null. log format can plain text, JSON, comma separated text. log format NULL TRYR_LOG_FORMAT environment variables checked. unset null, format considered plain text. logging message formed combining title message parts. log info also contains process ID, timestamp (using Sys.time()), log level. timestamp prints fractional seconds according digits. digits NULL checks TRYR_LOG_DIGITS environment variables uses value. default 3 TRYR_LOG_DIGITS unset null. Besides usual log levels, extra one \"SUCCESS\" used signal successful HTTP response codes (2xx).","code":""},{"path":[]},{"path":"https://hub.analythium.io/tryr/reference/msg.html","id":"ref-examples","dir":"Reference","previous_headings":"","what":"Examples","title":"Write Logging Information to STDOUT or STDERR — msg","text":"","code":"n <- 5 \"Sample \" %+% \"size \" %+% \"n = \" %+% n %+% \".\" #> [1] \"Sample size n = 5.\" msg(\"Success\", \"We did it!\") #> 6713 | 2024-05-23 02:52:11.320 [INFO ] Success - We did it! msg(\"Success\", \"We did it!\", \"SUCCESS\") #> 6713 | 2024-05-23 02:52:11.320 [SUCCESS] Success - We did it! msg(\"Error\", \"Oh no! n cannot be \" %+% n, \"ERROR\") msg(\"Success\", \"We did it!\", \"SUCCESS\", format = \"JSON\") #> {\"pid\":\"6713\",\"ts\":\"2024-05-23 02:52:11.321\",\"ut\":1716432731.32153,\"level\":\"SUCCESS\",\"value\":4,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Success\", \"We did it!\", format = \"JSON\") #> {\"pid\":\"6713\",\"ts\":\"2024-05-23 02:52:11.321\",\"ut\":1716432731.32191,\"level\":\"INFO\",\"value\":3,\"title\":\"Success\",\"message\":\"We did it!\"} msg(\"Error\", \"Oh no ...\", \"ERROR\", format = \"JSON\") msg(\"Success\", \"We did it!\", digits = 0) #> 6713 | 2024-05-23 02:52:11 [INFO ] Success - We did it! msg(\"Success\", \"We did it!\", digits = 6) #> 6713 | 2024-05-23 02:52:11.323054 [INFO ] Success - We did it!"},{"path":"https://hub.analythium.io/tryr/news/index.html","id":"version-011","dir":"Changelog","previous_headings":"","what":"Version 0.1.1","title":"Version 0.1.1","text":"Single quoting R packages DESCRIPTION.","code":""},{"path":"https://hub.analythium.io/tryr/news/index.html","id":"version-010","dir":"Changelog","previous_headings":"","what":"Version 0.1.0","title":"Version 0.1.0","text":"First stable release failed CRAN submission.","code":""}]