Skip to content

Commit

Permalink
Refactor error handling of the repository (#233)
Browse files Browse the repository at this point in the history
Co-authored-by: sorhawell <sorhawell@gmail.com>
Co-authored-by: eitsupi <50911393+eitsupi@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 21, 2023
1 parent 0b7b8d3 commit 71f3d05
Show file tree
Hide file tree
Showing 40 changed files with 1,596 additions and 920 deletions.
4 changes: 4 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Collate:
'dataframe__frame.R'
'datatype.R'
'docs.R'
'error_conversion.R'
'error_trait.R'
'expr__binary.R'
'expr__categorical.R'
'expr__datetime.R'
Expand All @@ -75,9 +77,11 @@ Collate:
'pkg-knitr.R'
'pkg-nanoarrow.R'
'rlang.R'
'rpolarserr.R'
'rust_result.R'
's3_methods.R'
'series__series.R'
'string_error.R'
'translation.R'
'vctrs.R'
'zzz.R'
Expand Down
6 changes: 5 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ S3method("$",ProtoExprArray)
S3method("$",RField)
S3method("$",RNullValues)
S3method("$",RPolarsDataType)
S3method("$",RPolarsErr)
S3method("$",Series)
S3method("$",VecDataFrame)
S3method("$",When)
Expand Down Expand Up @@ -64,6 +65,7 @@ S3method("[[",ProtoExprArray)
S3method("[[",RField)
S3method("[[",RNullValues)
S3method("[[",RPolarsDataType)
S3method("[[",RPolarsErr)
S3method("[[",Series)
S3method("[[",VecDataFrame)
S3method("[[",When)
Expand All @@ -77,13 +79,15 @@ S3method(.DollarNames,GroupBy)
S3method(.DollarNames,LazyFrame)
S3method(.DollarNames,PolarsBackgroundHandle)
S3method(.DollarNames,RField)
S3method(.DollarNames,RPolarsErr)
S3method(.DollarNames,Series)
S3method(.DollarNames,VecDataFrame)
S3method(.DollarNames,When)
S3method(.DollarNames,WhenThen)
S3method(.DollarNames,WhenThenThen)
S3method(.DollarNames,method_environment)
S3method(.DollarNames,polars_option_list)
S3method(as.character,RPolarsErr)
S3method(as.character,Series)
S3method(as.data.frame,DataFrame)
S3method(as.data.frame,LazyFrame)
Expand Down Expand Up @@ -120,6 +124,7 @@ S3method(print,PTime)
S3method(print,PolarsBackgroundHandle)
S3method(print,RField)
S3method(print,RPolarsDataType)
S3method(print,RPolarsErr)
S3method(print,Series)
S3method(print,When)
S3method(print,WhenThen)
Expand Down Expand Up @@ -148,7 +153,6 @@ export(nrow.DataFrame)
export(pl)
export(read_csv_)
export(scan_parquet)
export(unwrap)
importFrom(stats,median)
importFrom(stats,na.omit)
importFrom(utils,.DollarNames)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- `<DataFrame>$unique()` and `<LazyFrame>$unique()` gain a `maintain_order` argument (#238).
- New `pl$LazyFrame()` to quickly create a `LazyFrame`, mostly in examples or
for demonstration purposes (#240).
- Polars is internally moving away from string errors to a new error-type called `RPolarsErr` both on rust- and R-side. Final error messages should look very similar (#233).

# polars 0.6.1
## What's changed
Expand Down
2 changes: 1 addition & 1 deletion R/after-wrappers.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extendr_method_to_pure_functions = function(env, class_name = NULL) {
.pr$WhenThenThen = extendr_method_to_pure_functions(WhenThenThen)
.pr$VecDataFrame = extendr_method_to_pure_functions(VecDataFrame)
.pr$RNullValues = extendr_method_to_pure_functions(RNullValues)

.pr$RPolarsErr = extendr_method_to_pure_functions(RPolarsErr)

# TODO remove export

Expand Down
88 changes: 88 additions & 0 deletions R/error_conversion.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# THIS FILE IMPLEMENTS ERROR CONVERSION, FOR R TO Result-list & FOR Result-list TO R

# TODO unwrap should be eventually renamed to unwrap_with_context (or similar)
# a simpler unwrap without where_in and when_calling should be defined in rust_result.R

#' rust-like unwrapping of result. Useful to keep error handling on the R side.
#'
#' @param result a list here either element ok or err is NULL, or both if ok is litteral NULL
#' @param call context of error or string
#' @param context a msg to prefix a raised error with
#'
#' @return the ok-element of list , or a error will be thrown
#' @keywords internal
#' @examples
#'
#' # get unwrap without using :::
#' unwrap = environment(polars::pl$all)$unwrap
#'
#' structure(list(ok = "foo", err = NULL), class = "extendr_result")
#'
#' tryCatch(
#' unwrap(
#' structure(
#' list(ok = NULL, err = "something happen on the rust side"),
#' class = "extendr_result"
#' )
#' ),
#' error = function(err) as.character(err)
#' )
unwrap = function(result, context = NULL, call = sys.call(1L)) {
if (is_ok(result)) {
result$ok
} else {
result$err |>
where_in(context) |>
when_calling(call) |>
to_condition() |>
stop()
}
}

#' rust-like unwrap_err, internal use only
#' @details
#' throwed error info is sparse because only for internal errors
#' @keywords internal
#' @param result a Result, see rust_result.R#'
#' @return some error type
unwrap_err = function(result) {
if (is_ok(result)) {
stop("internal error: Cannot unwrap_err an Ok-value")
} else {
result$err
}
}


#' Capture any R error and return a rust-like Result
#' @param expr code to capture any error from and wrap as Result
#' @param msg handy way to add a context msg
#' @keywords internal
#' @return Result
#' @examples
#'
#' # user internal functions without using :::
#' result = environment(polars::pl$all)$result
#' unwrap_err = environment(polars::pl$all)$unwrap_err
#' unwrap = environment(polars::pl$all)$unwrap
#' Err = environment(polars::pl$all)$Err
#'
#' # capture regular R errors or RPolarsErr
#' throw_simpleError = \() stop("Imma simple error")
#' result(throw_simpleError())
#'
#' throw_RPolarsErr = \() unwrap(
#' Err(.pr$RPolarsErr$new()$bad_robj(42)$mistyped("String")$when("doing something"))
#' )
#' res_RPolarsErr = result(throw_RPolarsErr())
#' str(res_RPolarsErr)
#' RPolarsErr = unwrap_err(res_RPolarsErr)
#' RPolarsErr$contexts()
result = function(expr, msg = NULL) {
tryCatch(
Ok(expr),
error = \(cond) cond$value %||% cond$message |>
plain(msg) |>
Err()
)
}
93 changes: 93 additions & 0 deletions R/error_trait.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ANY NEW ERROR MUST IMPLEMENT THESE S3 METHODS, these are the "trait" of a polars error
# ALSO MUST IMPLEMENT BASE THESE METHODS: print

#' Internal generic method to add call to error
#' @param err any type which impl as.character
#' @param call calling context
#' @details
#' Additional details...
#'
#' @keywords internal
#' @return err as string
#' @examples
#' #
when_calling = function(err, call) {
if (polars_optenv$do_not_repeat_call || is.null(call)) {
err
} else {
UseMethod("when_calling", err)
}
}
when_calling.default = function(err, call) {
stop("internal error: an error-type was not fully implemented")
}
# support function to convert a call to a string
call_to_string = function(call) {
paste(
"\n",
paste(capture.output(print(call)), collapse = "\n")
)
}


#' Internal generic method to point to which public method the user got wrong
#' @param err any type which impl as.character
#' @param call calling context
#' @keywords internal
#' @return err as string
#' @examples
#' #
where_in = function(err, context) {
if (is.null(context)) {
return(err)
}
if (!is_string(context)) {
stop(
paste(
"internal error: where_in context must be a string or NULL it was: ",
str_string(context)
)
)
}
UseMethod("where_in", err)
}
where_in.default = function(err, context) {
stop("internal error: an error-type was not fully implemented")
}

#' Internal generic method to convert an error_type to condition.
#' @param err any type which impl as.character
#' @param call calling context
#' @keywords internal
#' @details
#' this method is needed to preserve state of err without upcasting to a string message
#' an implementation will describe how to store the error in the condition
#' @return condition
to_condition = function(err) {
UseMethod("to_condition", err)
}
to_condition.default = function(err) {
errorCondition(
paste(capture.output(print(err)), collapse = "\n"),
class = c("default_error"),
value = err,
call = NULL
)
}



#' Internal generic method to add plain text to error message
#' @param err some error type object
#' @param msg string to add
#' @keywords internal
#' @return condition
plain = function(err, msg) {
if (is.null(msg)) {
return(err)
}
UseMethod("plain", err)
}
plain.default = function(err, msg) {
paste0(msg, ": ", err)
}
Loading

0 comments on commit 71f3d05

Please sign in to comment.