From 37c151efe563f77b0298f1a94b07d01229735aea Mon Sep 17 00:00:00 2001 From: Tim Basel Date: Sat, 12 Feb 2022 10:54:10 +0100 Subject: [PATCH] docs, builtin, encoding.csv: update error implementations (#13440) --- doc/docs.md | 34 ++++++++++++++++++ vlib/builtin/js/builtin.v | 13 +++---- vlib/builtin/option.v | 13 +++---- vlib/encoding/csv/reader.v | 46 +++++++++++++++--------- vlib/encoding/csv/writer.v | 2 +- vlib/v/tests/string_optional_none_test.v | 10 +++++- 6 files changed, 81 insertions(+), 37 deletions(-) diff --git a/doc/docs.md b/doc/docs.md index e78ab8990a..1f4a20a88b 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -113,6 +113,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h * [Sum types](#sum-types) * [Type aliases](#type-aliases) * [Option/Result types & error handling](#optionresult-types-and-error-handling) +* [Custom error types](#custom-error-types) * [Generics](#generics) * [Concurrency](#concurrency) * [Spawning Concurrent Tasks](#spawning-concurrent-tasks) @@ -3351,6 +3352,39 @@ if resp := http.get('https://google.com') { Above, `http.get` returns a `?http.Response`. `resp` is only in scope for the first `if` branch. `err` is only in scope for the `else` branch. + +## Custom error types + +V gives you the ability to define custom error types through the `IError` interface. +The interface requires two methods: `msg() string` and `code() int`. Every type that +implements these methods can be used as an error. + +When defining a custom error type it is recommended to embed the builtin `Error` default +implementation. This provides an empty default implementation for both required methods, +so you only have to implement what you really need, and may provide additional utility +functions in the future. + +```v +struct PathError { + Error + path string +} + +fn (err PathError) msg() string { + return 'Failed to open path: $err.path' +} + +fn try_open(path string) ? { + return IError(PathError{ + path: path + }) +} + +fn main() { + try_open('/tmp') or { panic(err) } +} +``` + ## Generics ```v wip diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index a9e7e26029..883bf3919a 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -35,8 +35,9 @@ pub fn (err IError) str() string { } else { // >> Hack to allow old style custom error implementations - // TODO: can be removed once the checker 'hacks' are merged (so `vc` has them included) - if !isnil(err.msg) { + // TODO: remove once deprecation period for `IError` methods has ended + old_error_style := unsafe { voidptr(&err.msg) != voidptr(&err.code) } // if fields are not defined (new style) they don't have an offset between them + if old_error_style { '$err.type_name(): $err.msg' } else { // << @@ -47,13 +48,7 @@ pub fn (err IError) str() string { } // Error is the empty default implementation of `IError`. -pub struct Error { - // >> Hack to allow old style custom error implementations - // TODO: can be removed once the checker 'hacks' are merged (so `vc` has them included) - msg string - code int - /// << -} +pub struct Error {} pub fn (err Error) msg() string { return '' diff --git a/vlib/builtin/option.v b/vlib/builtin/option.v index d368f3a96a..40a9f376b0 100644 --- a/vlib/builtin/option.v +++ b/vlib/builtin/option.v @@ -26,8 +26,9 @@ pub fn (err IError) str() string { } else { // >> Hack to allow old style custom error implementations - // TODO: can be removed once the checker 'hacks' are merged (so `vc` has them included) - if !isnil(err.msg) { + // TODO: remove once deprecation period for `IError` methods has ended + old_error_style := unsafe { voidptr(&err.msg) != voidptr(&err.code) } // if fields are not defined (new style) they don't have an offset between + if old_error_style { '$err.type_name(): $err.msg' } else { // << @@ -38,13 +39,7 @@ pub fn (err IError) str() string { } // Error is the empty default implementation of `IError`. -pub struct Error { - // >> Hack to allow old style custom error implementations - // TODO: can be removed once the checker 'hacks' are merged (so `vc` has them included) - msg string - code int - /// << -} +pub struct Error {} pub fn (err Error) msg() string { return '' diff --git a/vlib/encoding/csv/reader.v b/vlib/encoding/csv/reader.v index 1865e454e1..55bf5b8d8e 100644 --- a/vlib/encoding/csv/reader.v +++ b/vlib/encoding/csv/reader.v @@ -6,24 +6,36 @@ module csv // Once interfaces are further along the idea would be to have something similar to // go's io.reader & bufio.reader rather than reading the whole file into string, this // would then satisfy that interface. I designed it this way to be easily adapted. -struct ErrCommentIsDelimiter { - msg string = 'encoding.csv: comment cannot be the same as delimiter' - code int +struct CommentIsDelimiterError { + Error } -struct ErrInvalidDelimiter { - msg string = 'encoding.csv: invalid delimiter' - code int +fn (err CommentIsDelimiterError) msg() string { + return 'encoding.csv: comment cannot be the same as delimiter' } -struct ErrEndOfFile { - msg string = 'encoding.csv: end of file' - code int +struct InvalidDelimiterError { + Error } -struct ErrInvalidLineEnding { - msg string = 'encoding.csv: could not find any valid line endings' - code int +fn (err InvalidDelimiterError) msg() string { + return 'encoding.csv: invalid delimiter' +} + +struct EndOfFileError { + Error +} + +fn (err EndOfFileError) msg() string { + return 'encoding.csv: end of file' +} + +struct InvalidLineEndingError { + Error +} + +fn (err InvalidLineEndingError) msg() string { + return 'encoding.csv: could not find any valid line endings' } struct Reader { @@ -72,7 +84,7 @@ pub fn (mut r Reader) read() ?[]string { fn (mut r Reader) read_line() ?string { // last record if r.row_pos == r.data.len { - return IError(&ErrEndOfFile{}) + return IError(&EndOfFileError{}) } le := if r.is_mac_pre_osx_le { '\r' } else { '\n' } mut i := r.data.index_after(le, r.row_pos) @@ -84,7 +96,7 @@ fn (mut r Reader) read_line() ?string { r.is_mac_pre_osx_le = true } else { // no valid line endings found - return IError(&ErrInvalidLineEnding{}) + return IError(&InvalidLineEndingError{}) } } else { // No line ending on file @@ -102,10 +114,10 @@ fn (mut r Reader) read_line() ?string { fn (mut r Reader) read_record() ?[]string { if r.delimiter == r.comment { - return IError(&ErrCommentIsDelimiter{}) + return IError(&CommentIsDelimiterError{}) } if !valid_delim(r.delimiter) { - return IError(&ErrInvalidDelimiter{}) + return IError(&InvalidDelimiterError{}) } mut need_read := true mut keep_raw := false @@ -185,7 +197,7 @@ fn (mut r Reader) read_record() ?[]string { } } if i <= -1 && fields.len == 0 { - return IError(&ErrInvalidDelimiter{}) + return IError(&InvalidDelimiterError{}) } } return fields diff --git a/vlib/encoding/csv/writer.v b/vlib/encoding/csv/writer.v index 8e63d874d5..07bd26c59d 100644 --- a/vlib/encoding/csv/writer.v +++ b/vlib/encoding/csv/writer.v @@ -23,7 +23,7 @@ pub fn new_writer() &Writer { // write writes a single record pub fn (mut w Writer) write(record []string) ?bool { if !valid_delim(w.delimiter) { - return IError(&ErrInvalidDelimiter{}) + return IError(&InvalidDelimiterError{}) } le := if w.use_crlf { '\r\n' } else { '\n' } for n, field_ in record { diff --git a/vlib/v/tests/string_optional_none_test.v b/vlib/v/tests/string_optional_none_test.v index c71cc00c6d..e789db1589 100644 --- a/vlib/v/tests/string_optional_none_test.v +++ b/vlib/v/tests/string_optional_none_test.v @@ -1,6 +1,14 @@ struct MyError { - code int msg string + code int +} + +fn (err MyError) msg() string { + return err.msg +} + +fn (err MyError) code() int { + return err.code } fn foo() int | none | IError {