├── tasks ├── 02-c-errors-concept │ ├── allocator │ │ ├── testdata │ │ │ ├── 8.txt │ │ │ ├── 1.txt │ │ │ ├── 3.txt │ │ │ ├── 4.txt │ │ │ ├── 5.txt │ │ │ ├── 6.txt │ │ │ ├── 2.txt │ │ │ └── 7.txt │ │ ├── allocator.h │ │ ├── allocator.suppr │ │ ├── main.c │ │ └── Makefile │ ├── allocator-errno_t │ │ ├── testdata │ │ │ ├── 5.txt │ │ │ ├── 8.txt │ │ │ ├── 1.txt │ │ │ ├── 3.txt │ │ │ ├── 4.txt │ │ │ ├── 6.txt │ │ │ ├── 2.txt │ │ │ └── 7.txt │ │ ├── allocator.h │ │ ├── allocator.suppr │ │ ├── main.c │ │ └── Makefile │ └── developers-everyday-life │ │ ├── testdata │ │ ├── 3.json │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 5.json │ │ ├── 6.json │ │ └── 4.json │ │ ├── get_user_handler.h │ │ ├── main.c │ │ ├── Makefile │ │ ├── db.h │ │ └── marshalers.h ├── 04-non-standard-modules │ ├── is-any │ │ └── is_any.go │ ├── wrap-nil │ │ ├── errors.go │ │ └── errors_test.go │ ├── trim-stacktrace │ │ ├── errors.go │ │ └── errors_test.go │ ├── wrapper │ │ ├── wrapper.go │ │ └── wrapper_example_test.go │ ├── deepest-stacktrace │ │ └── trace.go │ ├── uber-multierr-append-invoke-defer-gotcha │ │ ├── closer_mock.go │ │ └── main.go │ ├── combine-errors │ │ ├── combine.go │ │ └── combine_benchmark_test.go │ ├── cockroach-different-wraps │ │ └── main.go │ ├── hashicorp-multierror-gotcha-1 │ │ └── main.go │ ├── handmade-stacktrace │ │ ├── stack_trace.go │ │ └── stack_trace_test.go │ ├── hashicorp-multierror-gotcha-2 │ │ └── main.go │ ├── hashicorp-multierror-gotcha-3 │ │ └── main.go │ ├── hashicorp-multierror-replacement │ │ └── map.go │ ├── cause-and-is │ │ └── main.go │ ├── uber-multierr-append-invoke-named-gotcha │ │ └── main.go │ └── pkg-errors-wrap-nil-gotcha │ │ └── main.go ├── 03-go-errors-concept │ ├── errorf-on-steroids │ │ └── errors.go │ ├── unwrap-loop │ │ └── unwrap.go │ ├── fix-opaque-errors │ │ ├── is_temporary.go │ │ └── is_temporary_test.go │ ├── extract │ │ ├── extract.go │ │ └── extract_test.go │ ├── allocator │ │ └── allocator.go │ ├── ast-pain │ │ └── stmt.go │ ├── errors-is │ │ └── pipeline_err.go │ ├── errors-factory │ │ ├── errors.go │ │ └── errors_test.go │ ├── with-time-error │ │ ├── with_time_error.go │ │ └── with_time_error_test.go │ ├── errors-is-via-errors-as │ │ ├── pipeline_err.go │ │ └── pipeline_err_test.go │ ├── safe-factorial │ │ ├── factorial.go │ │ └── factorial_test.go │ ├── error-embedding │ │ ├── err_test.go │ │ └── err.go │ ├── errors-as │ │ └── pipeline_err.go │ ├── error-types │ │ └── main.go │ ├── from-error-to-error │ │ └── main.go │ ├── validation-errors │ │ └── search_request.go │ ├── handling-error-types │ │ ├── errors.go │ │ ├── handler.go │ │ └── handler_test.go │ ├── byte-reader-byte-writer │ │ └── byte_buffer.go │ ├── handling-opaque-errors │ │ ├── errors.go │ │ └── handler.go │ ├── handling-sentinel-errors │ │ ├── handler.go │ │ └── handler_test.go │ ├── broken-chain │ │ ├── chain.go │ │ └── chain_test.go │ ├── handmade-stack │ │ └── handler.go │ └── file-load-err │ │ └── main.go ├── 05-errors-best-practices │ ├── empty-struct-problem │ │ ├── pkga │ │ │ └── errors.go │ │ └── pkgb │ │ │ └── errors.go │ ├── json-writer │ │ ├── server │ │ │ ├── json_writer.go │ │ │ └── server.go │ │ └── main.go │ ├── read-by-chunk │ │ └── reader.go │ ├── docker-err │ │ ├── error.go │ │ └── docker.go │ ├── pretty-error │ │ ├── pretty.go │ │ └── pretty_test.go │ ├── error-context │ │ ├── context.go │ │ └── context_test.go │ ├── pointer-error │ │ ├── ptr_err.go │ │ └── ptr_err_test.go │ ├── gotcha-err-iface-1 │ │ ├── handler.go │ │ ├── http_error.go │ │ └── handler_test.go │ ├── http-error │ │ ├── http_error.go │ │ └── http_error_test.go │ ├── template-err │ │ ├── tmpl.go │ │ └── tmpl_test.go │ ├── idempotent-wrap │ │ ├── wrap.go │ │ └── wrap_test.go │ ├── template-err-opaque │ │ └── tmpl.go │ ├── gotcha-err-iface-3 │ │ ├── ops.go │ │ └── ops_test.go │ ├── mini-word │ │ ├── document.go │ │ └── document_bench_test.go │ └── monad │ │ └── monad.go ├── 07-working-with-errors-in-concurrency │ ├── command-executor │ │ ├── errors.go │ │ └── transport.go │ ├── hashing-pipeline │ │ ├── hash_test.go │ │ ├── hash.go │ │ └── hash_tree.go │ ├── errgroup-basic-1 │ │ └── main.go │ ├── errgroup-structure-filling │ │ └── get_order.go │ ├── errgroup-ctx-cancelling-issue │ │ └── main.go │ ├── errgroup-basic-2 │ │ └── main.go │ ├── cats-unmarshalling-gotcha │ │ └── main.go │ ├── errgroup-with-context │ │ └── main.go │ ├── errgroup-collector │ │ └── collector.go │ └── errgroup-task-runner │ │ └── task_runner.go └── 06-working-with-errors-in-tests │ ├── divide-et-impera │ ├── jwt.go │ ├── jwt_structs.go │ └── jwt_errors.go │ ├── parse-token-for-security │ ├── jwt_structs.go │ ├── jwt_errors.go │ ├── jwt_private.go │ └── jwt.go │ ├── get-index-from-filename │ ├── index_bench_test.go │ └── index.go │ └── network-err-mock │ └── fetcher.go ├── .gitignore ├── examples ├── 04-non-standard-modules │ ├── cockroach-grpc │ │ ├── NOTICE │ │ ├── echoer.proto │ │ ├── status │ │ │ └── status.go │ │ ├── middleware │ │ │ └── client.go │ │ ├── server_test.go │ │ └── main_test.go │ ├── pkg-expensive-stack-wrap │ │ ├── main.go │ │ ├── wrap_pkg.go │ │ ├── wrap_std.go │ │ └── main_test.go │ ├── cockroach-expensive-stack │ │ ├── main.go │ │ ├── wrap_pkg.go │ │ ├── wrap_cockroach.go │ │ └── main_test.go │ ├── pkg-expensive-stack-wrap-with-msg │ │ ├── main.go │ │ ├── wrap_std.go │ │ ├── wrap_msg_and_stack.go │ │ ├── wrap_msg_only.go │ │ └── main_test.go │ ├── pkg-new │ │ └── main.go │ ├── cockroach-wrap │ │ └── main.go │ ├── pkg-wrap-vs-fmt-4 │ │ └── main.go │ ├── cockroach-is-any │ │ └── main.go │ ├── pkg-expensive-stack-new │ │ ├── main_test.go │ │ └── main.go │ ├── emperror-logging │ │ └── main.go │ ├── pkg-wrap-with-message │ │ └── main.go │ ├── cockroach-mark │ │ └── main.go │ ├── pkg-wrap-vs-fmt-2 │ │ └── main.go │ ├── emperror-panic-and-recover │ │ ├── client-server │ │ │ ├── server │ │ │ │ ├── middleware.go │ │ │ │ ├── handler.go │ │ │ │ └── server.go │ │ │ └── client │ │ │ │ └── client.go │ │ └── simple │ │ │ └── main.go │ ├── cockroach-if │ │ └── main.go │ ├── pkg-wrap-vs-fmt-1 │ │ └── main.go │ ├── cockroach-with-secondary-error │ │ └── main.go │ ├── pkg-wrap-vs-fmt-3 │ │ └── main.go │ ├── emperror-filtering │ │ └── main.go │ ├── cockroach-mark-gotcha │ │ └── main.go │ ├── pkg-wrap-in-diff-ways │ │ └── main.go │ ├── hashicorp-multierror │ │ └── main.go │ ├── cockroach-unwrap │ │ └── main.go │ ├── cockroach-is-any-bug │ │ └── main.go │ ├── cockroach-combine-errors │ │ └── main.go │ ├── pkg-wrap │ │ └── main.go │ └── uber-multierr-append-invoke │ │ └── main.go ├── 05-errors-best-practices │ ├── constant-errors-diff-pkgs │ │ ├── common │ │ │ └── errors.go │ │ ├── pkga │ │ │ └── errors.go │ │ ├── pkgb │ │ │ └── errors.go │ │ └── main.go │ ├── constant-errors-rsa-example │ │ └── main.go │ ├── constant-errors-problem │ │ ├── dirtyhacker │ │ │ └── hack.go │ │ └── main.go │ ├── constant-errors │ │ └── main.go │ ├── api-borders │ │ ├── 02_db_user_opaq_err.go │ │ ├── 01_db_user_own_err.go │ │ ├── 01_db_user_own_private_err.go │ │ ├── 0_db_user_original.go │ │ └── db_user_test.go │ ├── gofmt-rewrite-rules │ │ ├── .golangci.yml │ │ └── main.go │ ├── govet-erroras │ │ └── main.go │ ├── ruleguard-wrap │ │ ├── example.go │ │ └── gorules │ │ │ └── rules.go │ ├── railway-bufio-writer │ │ └── main.go │ ├── std-no-pointers │ │ └── main.go │ ├── gotcha-func-call │ │ └── main.go │ ├── gotcha-err-iface │ │ └── main.go │ ├── errorf-bench │ │ └── errorf_bench_test.go │ ├── equal-err-text │ │ └── main.go │ ├── resp-body-close │ │ └── main.go │ ├── not-found-error-constructor │ │ └── main.go │ ├── concurrent-event-publishing │ │ ├── job_test.go │ │ └── job.go │ ├── shadowed-err-2 │ │ └── main.go │ ├── shadowed-err-1 │ │ └── main.go │ ├── idempotent-http-server-listen │ │ └── main.go │ ├── idempotent-bufio-writer-flush │ │ └── main.go │ ├── resp-body-close-func │ │ └── main.go │ ├── write-to-file │ │ └── main.go │ ├── naming │ │ └── main.go │ ├── not-found-error │ │ └── main.go │ ├── status-error │ │ └── main.go │ ├── write-resp-err-conn │ │ └── main.go │ ├── exec-err-pain │ │ └── main.go │ ├── user-registration-request-multierr │ │ └── main.go │ └── user-registration-request-monad-2 │ │ └── user_registration_request.go ├── 08-future-of-errors-in-go2 │ ├── try │ │ ├── print_sum.go │ │ └── copy_file.go │ ├── copy-file │ │ ├── v1.go │ │ ├── v2.go │ │ ├── v3_test.go │ │ └── v3.go │ ├── error-printing │ │ └── main.go │ └── check-handle │ │ └── print_sum.go ├── 03-go-errors-concept │ ├── errorf-2 │ │ └── main.go │ ├── errorf-4 │ │ └── main.go │ ├── errors-is-and-v │ │ └── main.go │ ├── errorf-3 │ │ └── main.go │ ├── error-formatting │ │ └── main.go │ ├── errorf-1 │ │ └── main.go │ ├── single-unwrap │ │ └── main.go │ ├── opaque-errors │ │ └── main.go │ ├── users-sort │ │ └── main.go │ ├── error-with-stacktrace │ │ └── main.go │ ├── error-with-stacktrace-opaque │ │ └── main.go │ ├── byte-buffer-initial │ │ └── main.go │ ├── file-load-err │ │ └── main.go │ ├── cancellation-cause │ │ └── main.go │ ├── file-load-err-wrap │ │ └── main.go │ ├── byte-buffer-with-errors │ │ └── main.go │ └── no-wrap-pain │ │ └── main.go ├── 02-c-errors-concept │ ├── perror.c │ ├── perror_cases.c │ ├── unexistent_file.c │ ├── custom_err_value.c │ └── validation.c ├── 07-working-with-errors-in-concurrency │ ├── errgroup-multiple-errors │ │ └── main.go │ ├── errgroup-try-go │ │ └── main.go │ ├── hashicorp-multierror-group │ │ └── main.go │ ├── errgroup-simple-example │ │ └── main.go │ ├── errgroup-with-context │ │ └── network_request.go │ ├── cats-unmarshalling │ │ └── main.go │ ├── waitgroup-simple-example │ │ └── main.go │ ├── errgroup-limited │ │ └── main.go │ ├── cats-unmarshalling-err-channel │ │ └── main.go │ ├── cats-unmarshalling-err-container │ │ └── main.go │ └── errgroup-with-context-diff-parent │ │ └── main.go └── 06-working-with-errors-in-tests │ ├── strconv-errors │ └── main.go │ ├── testify-gotchas │ └── example_test.go │ └── testify-require-and-assert │ └── example_test.go ├── README.md └── Taskfile.yml /tasks/02-c-errors-concept/allocator/testdata/8.txt: -------------------------------------------------------------------------------- 1 | 1 1 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/1.txt: -------------------------------------------------------------------------------- 1 | 777 1024 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/3.txt: -------------------------------------------------------------------------------- 1 | 777 100 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/4.txt: -------------------------------------------------------------------------------- 1 | 777 1025 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/5.txt: -------------------------------------------------------------------------------- 1 | 777 0 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/5.txt: -------------------------------------------------------------------------------- 1 | 777 0 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/8.txt: -------------------------------------------------------------------------------- 1 | 1 1 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/6.txt: -------------------------------------------------------------------------------- 1 | 777 10000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .idea/ 4 | .vscode/ 5 | 6 | *.out 7 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/1.txt: -------------------------------------------------------------------------------- 1 | 777 1024 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/3.txt: -------------------------------------------------------------------------------- 1 | 777 100 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/4.txt: -------------------------------------------------------------------------------- 1 | 777 1025 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/6.txt: -------------------------------------------------------------------------------- 1 | 777 10000 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/2.txt: -------------------------------------------------------------------------------- 1 | 1 100000000000 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/2.txt: -------------------------------------------------------------------------------- 1 | 1 100000000000 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/testdata/7.txt: -------------------------------------------------------------------------------- 1 | 777 18446744073709551615 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/3.json: -------------------------------------------------------------------------------- 1 | {"user_id"} -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/testdata/7.txt: -------------------------------------------------------------------------------- 1 | 777 18446744073709551615 -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/1.json: -------------------------------------------------------------------------------- 1 | {"user_id": 4224} -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/2.json: -------------------------------------------------------------------------------- 1 | {"user_id": -100} -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/5.json: -------------------------------------------------------------------------------- 1 | {"user_id": 777} -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/6.json: -------------------------------------------------------------------------------- 1 | {"user_id": 0} -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/testdata/4.json: -------------------------------------------------------------------------------- 1 | {"user_id": 10001} -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/NOTICE: -------------------------------------------------------------------------------- 1 | - https://github.com/cockroachdb/errors/tree/master/grpc by kena (https://github.com/knz) 2 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/is-any/is_any.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | func IsAny(err error, references ...error) bool { 4 | // Реализуй меня. 5 | return false 6 | } 7 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errorf-on-steroids/errors.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | func Errorf(format string, args ...any) error { 4 | // Реализуй меня. 5 | return nil 6 | } 7 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-diff-pkgs/common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Error string 4 | 5 | func (e Error) Error() string { return string(e) } 6 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-rsa-example/main.go: -------------------------------------------------------------------------------- 1 | package innocent 2 | 3 | import "crypto/rsa" 4 | 5 | func init() { 6 | rsa.ErrVerification = nil 7 | } 8 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/empty-struct-problem/pkga/errors.go: -------------------------------------------------------------------------------- 1 | package pkga 2 | 3 | type EOF struct{} 4 | 5 | func (b EOF) Error() string { 6 | return "end of file" 7 | } 8 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/empty-struct-problem/pkgb/errors.go: -------------------------------------------------------------------------------- 1 | package pkgb 2 | 3 | type EOF struct{} 4 | 5 | func (b EOF) Error() string { 6 | return "end of file" 7 | } 8 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-problem/dirtyhacker/hack.go: -------------------------------------------------------------------------------- 1 | package dirtyhacker 2 | 3 | import "io" 4 | 5 | func MutateEOF() { 6 | io.EOF = nil // Bugaga! 7 | } 8 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/json-writer/server/json_writer.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | // Реализуй меня. 4 | type jsonWriter struct{} 5 | 6 | func (jw jsonWriter) Write(v any) { 7 | // Реализуй меня. 8 | } 9 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/unwrap-loop/unwrap.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | type Unwrapper interface { 4 | Unwrap() error 5 | } 6 | 7 | func Unwrap(err error) error { 8 | // Реализуй меня. 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/wrap-nil/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // Wrapf работает аналогично fmt.Errorf, только поддерживает nil-ошибки. 4 | func Wrapf(err error, f string, v ...any) error { 5 | return nil 6 | } 7 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/trim-stacktrace/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // TrimStackTrace режет все стектрейсы в цепочке ошибок err. 4 | func TrimStackTrace(err error) error { 5 | // Реализуй меня. 6 | return err 7 | } 8 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/fix-opaque-errors/is_temporary.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | func IsTemporary(err error) bool { 4 | type t interface { 5 | IsTemporary() bool 6 | } 7 | // Реализуй меня. 8 | return false 9 | } 10 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // go run -tags std ./... 6 | // go run -tags pkg ./... 7 | func main() { 8 | fmt.Printf("%+v", GimmeDeepError(2)) 9 | } 10 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/read-by-chunk/reader.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import "io" 4 | 5 | var ErrInvalidChunkSize error 6 | 7 | func ReadByChunk(r io.Reader, chunkSize int) ([][]byte, error) { 8 | return nil, nil 9 | } 10 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-expensive-stack/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // go run -tags pkg ./... 6 | // go run -tags cockroach ./... 7 | func main() { 8 | fmt.Printf("%+v", GimmeDeepError(2)) 9 | } 10 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/allocator.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern int errno; 4 | 5 | #define ADMIN 777 6 | #define MIN_MEMORY_BLOCK 1024 7 | 8 | void *allocate(int user_id, size_t size) 9 | { 10 | // Реализуй меня. 11 | } 12 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/extract/extract.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | // Extract достаёт из цепочки err набор sentinel-ошибок, 4 | // игнорируя "оборачивающие" их ошибки. 5 | func Extract(err error) []error { 6 | // Реализуй меня. 7 | return nil 8 | } 9 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/wrapper/wrapper.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // Wrapper требует от типа быть ошибкой, поддерживающей API 4 | // как стандартной библиотеки, так и github/pkg/errors. 5 | type Wrapper interface { // Добавь интерфейсу методов. 6 | } 7 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/docker-err/error.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | type Error struct{} 4 | 5 | func (e *Error) Error() string { 6 | panic("implement me") 7 | } 8 | 9 | func newDockerError(err error) *Error { 10 | return &Error{} 11 | } 12 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/command-executor/errors.go: -------------------------------------------------------------------------------- 1 | package commandexecutor 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrUnsupportedCommand = errors.New("unsupported command") 7 | ErrCommandTimeout = errors.New("command timeout") 8 | ) 9 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/try/print_sum.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // Несуществующий синтаксис. 4 | 5 | /* 6 | func printSum(a, b string) error { 7 | fmt.Println("result:", try(strconv.Atoi(a)) + try(strconv.Atoi(b))) 8 | return nil 9 | } 10 | */ 11 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/allocator.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef int errno_t; 4 | 5 | #define ADMIN 777 6 | #define MIN_MEMORY_BLOCK 1024 7 | 8 | errno_t allocate(int user_id, size_t size, void **mem) 9 | { 10 | // Реализуй меня. 11 | } 12 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/pretty-error/pretty.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | // Pretty делает цепочку ошибок более читаемой – очередная заврапленная 4 | // ошибка будет представлена на новой строке. 5 | func Pretty(err error) error { 6 | // Реализуй меня. 7 | return nil 8 | } 9 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/errorf-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | fmt.Printf("cannot do operation: %w", context.Canceled) 10 | // cannot do operation: %!w(*errors.errorString=&{context canceled}) 11 | } 12 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/errorf-4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | err := fmt.Errorf("cannot do operation: %w", nil) 9 | fmt.Println(err) // cannot do operation: %!w() 10 | fmt.Println(err == nil) // false 11 | } 12 | -------------------------------------------------------------------------------- /examples/02-c-errors-concept/perror.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | FILE *fp; 7 | fp = fopen("unexistent_file.txt", "r"); 8 | 9 | perror("cannot open file"); // cannot open file: No such file or directory 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap-with-msg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // go run -tags std ./... 6 | // go run -tags pkg.msg.stack ./... 7 | // go run -tags pkg.msg.only ./... 8 | func main() { 9 | fmt.Printf("%+v", GimmeDeepError(2)) 10 | } 11 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/copy-file/v1.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // Несуществующий синтаксис. 4 | 5 | /* 6 | func CopyFileV1(src, dst string) throws error { 7 | r := os.Open(src) 8 | defer r.Close() 9 | 10 | w := os.Create(dst) 11 | io.Copy(w, r) 12 | w.Close() 13 | } 14 | */ 15 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/error-context/context.go: -------------------------------------------------------------------------------- 1 | package errctx 2 | 3 | type Fields map[string]any 4 | 5 | func AppendTo(err error, fields Fields) error { 6 | // Реализуй меня. 7 | return nil 8 | } 9 | 10 | func From(err error) Fields { 11 | // Реализуй меня. 12 | return nil 13 | } 14 | -------------------------------------------------------------------------------- /examples/02-c-errors-concept/perror_cases.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern int errno; 5 | 6 | int main() 7 | { 8 | errno = 0; 9 | perror("msg for zero errno\t"); 10 | 11 | errno = 4242; 12 | perror("msg for custom errno\t"); 13 | 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/pointer-error/ptr_err.go: -------------------------------------------------------------------------------- 1 | package ptrerror 2 | 3 | type PointerError struct { 4 | msg string 5 | } 6 | 7 | func (e *PointerError) Error() string { 8 | return e.msg 9 | } 10 | 11 | func NewPointerError(m string) PointerError { 12 | return PointerError{msg: m} 13 | } 14 | -------------------------------------------------------------------------------- /examples/02-c-errors-concept/unexistent_file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern int errno; 5 | 6 | int main() 7 | { 8 | FILE *fp; 9 | fp = fopen("unexistent_file.txt", "r"); 10 | 11 | printf("value of errno: %d\n", errno); // value of errno: 2 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/gotcha-err-iface-1/handler.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | func Handle() error { 4 | var err *HTTPError 5 | 6 | if err2 := usefulWork(); err2 != nil { 7 | err = ErrInternalServerError 8 | } 9 | return err 10 | } 11 | 12 | var usefulWork = func() error { 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/echoer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc; 4 | 5 | service Echoer { 6 | rpc Echo (EchoRequest) returns (EchoReply) {} 7 | } 8 | 9 | message EchoRequest { 10 | string text = 1; 11 | } 12 | 13 | message EchoReply { 14 | string reply = 1; 15 | } 16 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/divide-et-impera/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | func parseHeader(data []byte) (h Header, err error) { 4 | // Реализуй меня. 5 | return Header{}, nil 6 | } 7 | 8 | func parsePayload(data []byte) (t Token, err error) { 9 | // Реализуй меня. 10 | return Token{}, nil 11 | } 12 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors/main.go: -------------------------------------------------------------------------------- 1 | //nolint:deadcode 2 | package main 3 | 4 | type Error string 5 | 6 | func (e Error) Error() string { return string(e) } 7 | 8 | const EOF = Error("EOF") 9 | 10 | func main() { 11 | // cannot assign to EOF (declared const) 12 | // EOF = Error("EOF2") 13 | } 14 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/deepest-stacktrace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "github.com/getsentry/sentry-go" 5 | ) 6 | 7 | // GetDeepestStackTrace достаёт самый глубокий стектрейс из цепочки ошибок. 8 | func GetDeepestStackTrace(err error) *sentry.Stacktrace { 9 | // Реализуй меня. 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/errors-is-and-v/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | func main() { 10 | err := io.EOF 11 | 12 | fmt.Println( 13 | errors.Is(err, io.EOF)) // true 14 | fmt.Println( 15 | errors.Is(fmt.Errorf("cannot read file: %v", err), io.EOF)) // false 16 | } 17 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/api-borders/02_db_user_opaq_err.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | ) 7 | 8 | func IsNotFoundError(err error) bool { 9 | return errors.Is(err, sql.ErrNoRows) 10 | } 11 | 12 | func IsNotFoundError2(err error) bool { 13 | return errors.Is(err, errNotFound) 14 | } 15 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/allocator/allocator.go: -------------------------------------------------------------------------------- 1 | package allocator 2 | 3 | const ( 4 | Admin = 777 5 | MinMemoryBlock = 1024 6 | ) 7 | 8 | type NotPermittedError struct{} 9 | 10 | type ArgOutOfDomainError struct{} 11 | 12 | func Allocate(userID, size int) ([]byte, error) { 13 | // Реализуй меня. 14 | return nil, nil 15 | } 16 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-new/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func main() { 11 | err := errors.New("error happened") 12 | fmt.Printf("%+v", err) 13 | 14 | sErr := stderrors.New("error happened") 15 | fmt.Printf("\n---\n%+v", sErr) 16 | } 17 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/allocator.suppr: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:FishyValue 4 | malloc(size) 5 | fun:malloc 6 | fun:allocate 7 | fun:main 8 | } 9 | 10 | { 11 | 12 | Memcheck:FishyValue 13 | calloc(nmemb) 14 | fun:calloc 15 | fun:allocate 16 | fun:main 17 | } 18 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/ast-pain/stmt.go: -------------------------------------------------------------------------------- 1 | package astpain 2 | 3 | import ( 4 | "go/ast" 5 | ) 6 | 7 | // GetDeferredFunctionName возвращает имя функции, вызов которой был отложен через defer, 8 | // если входящий node является *ast.DeferStmt. 9 | func GetDeferredFunctionName(node ast.Node) string { 10 | // Реализуй меня. 11 | return "" 12 | } 13 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/allocator.suppr: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:FishyValue 4 | malloc(size) 5 | fun:malloc 6 | fun:allocate 7 | fun:main 8 | } 9 | 10 | { 11 | 12 | Memcheck:FishyValue 13 | calloc(nmemb) 14 | fun:calloc 15 | fun:allocate 16 | fun:main 17 | } 18 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/gofmt-rewrite-rules/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | gofmt: 3 | simplify: true 4 | rewrite-rules: 5 | - pattern: 'nil != err' 6 | replacement: 'err != nil' 7 | - pattern: 'err == nil' 8 | replacement: 'nil == err' 9 | 10 | linters: 11 | disable-all: true 12 | enable: 13 | - gofmt 14 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/gofmt-rewrite-rules/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func main() { 9 | if err := work(); nil != err { 10 | fmt.Println("error") 11 | } 12 | 13 | if err := work(); err == nil { 14 | fmt.Println("no error") 15 | } 16 | } 17 | 18 | func work() error { 19 | return io.EOF 20 | } 21 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/govet-erroras/main.go: -------------------------------------------------------------------------------- 1 | //nolint:govet 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | var err error 12 | var osErr *os.PathError 13 | if errors.As(err, osErr) { // Казалось бы, уже указатель! 14 | fmt.Println(osErr.Path) // До этой строчки не дойдёт :( 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/uber-multierr-append-invoke-defer-gotcha/closer_mock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "io" 4 | 5 | var _ io.Closer = (*CloserMock)(nil) 6 | 7 | type CloserMock struct { 8 | err error 9 | } 10 | 11 | func (c *CloserMock) SetErr(e error) { 12 | c.err = e 13 | } 14 | 15 | func (c *CloserMock) Close() error { 16 | return c.err 17 | } 18 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/http-error/http_error.go: -------------------------------------------------------------------------------- 1 | package httperr 2 | 3 | // Реализуй нас. 4 | var ( 5 | ErrStatusOK error 6 | ErrStatusBadRequest error 7 | ErrStatusNotFound error 8 | ErrStatusUnprocessableEntity error 9 | ErrStatusInternalServerError error 10 | ) 11 | 12 | // Реализуй меня. 13 | type HTTPError int 14 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/pointer-error/ptr_err_test.go: -------------------------------------------------------------------------------- 1 | package ptrerror 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestPointerError(t *testing.T) { 11 | p1 := NewPointerError("sky is falling") 12 | p2 := NewPointerError("sky is falling") 13 | require.False(t, errors.Is(p1, p2)) 14 | } 15 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/template-err/tmpl.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "html/template" 5 | "io" 6 | ) 7 | 8 | var ( 9 | errParseTemplate error 10 | errExecuteTemplate error 11 | ) 12 | 13 | func ParseAndExecuteTemplate(wr io.Writer, name, text string, data any) { 14 | t, _ := template.New(name).Parse(text) 15 | t.Execute(wr, data) 16 | } 17 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/hashing-pipeline/hash_test.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestHash_String(t *testing.T) { 10 | h := newHash([]byte("hello")) 11 | assert.Equal(t, "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50", h.String()) 12 | } 13 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-is/pipeline_err.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import "fmt" 4 | 5 | type PipelineError struct { 6 | User string 7 | Name string 8 | FailedSteps []string 9 | } 10 | 11 | func (p *PipelineError) Error() string { 12 | return fmt.Sprintf("pipeline %q error", p.Name) 13 | } 14 | 15 | // Добавь метод Is для типа *PipelineError. 16 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/divide-et-impera/jwt_structs.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | type Header struct { 4 | Alg string `json:"alg"` 5 | Typ string `json:"typ"` 6 | } 7 | 8 | type Token struct { 9 | Email string `json:"email"` 10 | Subject string `json:"subject"` 11 | Scopes []string `json:"scopes"` 12 | ExpiredAt int64 `json:"expired_at"` 13 | } 14 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/divide-et-impera/jwt_errors.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrInvalidHeaderEncoding = errors.New("invalid header encoding") 7 | ErrInvalidPayloadEncoding = errors.New("invalid payload encoding") 8 | 9 | ErrInvalidBase64 = errors.New("invalid base64") 10 | ErrInvalidJSON = errors.New("invalid json") 11 | ) 12 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/ruleguard-wrap/example.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import "github.com/pkg/errors" 4 | 5 | // $ ruleguard -rules gorules/rules.go example.go 6 | 7 | func foo() error { 8 | if err := bar(); err != nil { 9 | return err 10 | } 11 | return nil 12 | } 13 | 14 | func bar() error { 15 | return errors.Wrap(errors.New("bad request"), "do request") 16 | } 17 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-factory/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // NewError возвращает новое значение-ошибку, текст которой является msg. 4 | // Две ошибки с одинаковым текстом, созданные через NewError, не равны между собой: 5 | // 6 | // NewError("end of file") != NewError("end of file") 7 | func NewError(msg string) error { 8 | // Реализуй меня. 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/parse-token-for-security/jwt_structs.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | type Header struct { 4 | Alg string `json:"alg"` 5 | Typ string `json:"typ"` 6 | } 7 | 8 | type Token struct { 9 | Email string `json:"email"` 10 | Subject string `json:"subject"` 11 | Scopes []string `json:"scopes"` 12 | ExpiredAt int64 `json:"expired_at"` 13 | } 14 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap/wrap_pkg.go: -------------------------------------------------------------------------------- 1 | //go:build pkg 2 | // +build pkg 3 | 4 | package main 5 | 6 | import "github.com/pkg/errors" 7 | 8 | func GimmeDeepError(depth int) error { 9 | if depth == 1 { 10 | return errors.New("ooops, an error on level 1") 11 | } 12 | return errors.Wrapf(GimmeDeepError(depth-1), "error happened on level %d", depth) 13 | } 14 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/errorf-3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | err := fmt.Errorf("cannot do operation: %w", context.Canceled.Error()) 11 | fmt.Println(err) // cannot do operation: %!w(string=context canceled) 12 | fmt.Println(errors.Is(err, context.Canceled)) // false 13 | } 14 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/with-time-error/with_time_error.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type WithTimeError struct { // Реализуй меня. 8 | } 9 | 10 | func NewWithTimeError(err error) error { 11 | return newWithTimeError(err, time.Now) 12 | } 13 | 14 | func newWithTimeError(err error, timeFunc func() time.Time) error { 15 | // Реализуй меня. 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/combine-errors/combine.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // Combine "прицепляет" ошибки other к err так, что они начинают фигурировать при выводе 4 | // её на экран через спецификатор `%+v`. Если err является nil, то первостепенной ошибкой 5 | // становится первая из ошибок other. 6 | func Combine(err error, other ...error) error { 7 | // Реализуй меня. 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-expensive-stack/wrap_pkg.go: -------------------------------------------------------------------------------- 1 | //go:build pkg 2 | // +build pkg 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func GimmeDeepError(depth int) error { 11 | if depth == 1 { 12 | return errors.New("ooops, an error on level 1") 13 | } 14 | return errors.Wrapf(GimmeDeepError(depth-1), "error happened on level %d", depth) 15 | } 16 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap/wrap_std.go: -------------------------------------------------------------------------------- 1 | //go:build std 2 | // +build std 3 | 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | func GimmeDeepError(depth int) error { 12 | if depth == 1 { 13 | return errors.New("ooops, an error on level 1") 14 | } 15 | return fmt.Errorf("error happened on level %d: %w", depth, GimmeDeepError(depth-1)) 16 | } 17 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap-with-msg/wrap_std.go: -------------------------------------------------------------------------------- 1 | //go:build std 2 | // +build std 3 | 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | func GimmeDeepError(depth int) error { 12 | if depth == 1 { 13 | return errors.New("ooops, an error on level 1") 14 | } 15 | return fmt.Errorf("error happened on level %d: %w", depth, GimmeDeepError(depth-1)) 16 | } 17 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-basic-1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/sync/errgroup" 7 | ) 8 | 9 | func main() { 10 | var eg errgroup.Group 11 | 12 | eg.Go(func() error { 13 | fmt.Println("1") 14 | return nil 15 | }) 16 | 17 | eg.Go(func() error { 18 | fmt.Println("2") 19 | return nil 20 | }) 21 | 22 | fmt.Println("3") 23 | } 24 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/railway-bufio-writer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | //nolint:errcheck 10 | func main() { 11 | b := bufio.NewWriter(os.Stdout) 12 | 13 | b.WriteString("Hello ") 14 | b.WriteString("World") 15 | b.WriteString("!") 16 | 17 | if err := b.Flush(); err != nil { 18 | fmt.Println(err) 19 | } 20 | 21 | // Hello World! 22 | } 23 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/idempotent-wrap/wrap.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "github.com/pkg/errors" 4 | 5 | type stackTracer interface { 6 | StackTrace() errors.StackTrace 7 | } 8 | 9 | // Wrap оборачивает ошибку в сообщение. Также добавляет к ошибке стектрейс, 10 | // если в цепочке уже нет ошибки со стектрейсом. 11 | func Wrap(err error, msg string) error { 12 | // Реализуй меня. 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-diff-pkgs/pkga/errors.go: -------------------------------------------------------------------------------- 1 | package pkga 2 | 3 | import "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/constant-errors-diff-pkgs/common" 4 | 5 | type err string 6 | 7 | func (e err) Error() string { return string(e) } 8 | 9 | const ( 10 | ErrInvalidHost = err("invalid host") 11 | ErrUnknownData = common.Error("unknown data") 12 | ) 13 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-diff-pkgs/pkgb/errors.go: -------------------------------------------------------------------------------- 1 | package pkgb 2 | 3 | import "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/constant-errors-diff-pkgs/common" 4 | 5 | type err string 6 | 7 | func (e err) Error() string { return string(e) } 8 | 9 | const ( 10 | ErrInvalidHost = err("invalid host") 11 | ErrUnknownData = common.Error("unknown data") 12 | ) 13 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/error-formatting/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | func main() { 9 | for _, err := range []error{ 10 | fmt.Errorf("request failed: %v", io.EOF), 11 | fmt.Errorf("request failed: %v", io.EOF.Error()), 12 | fmt.Errorf("request failed: %s", io.EOF), 13 | fmt.Errorf("request failed: %s", io.EOF.Error()), 14 | } { 15 | fmt.Println(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-expensive-stack/wrap_cockroach.go: -------------------------------------------------------------------------------- 1 | //go:build cockroach 2 | // +build cockroach 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/cockroachdb/errors" 8 | ) 9 | 10 | func GimmeDeepError(depth int) error { 11 | if depth == 1 { 12 | return errors.New("ooops, an error on level 1") 13 | } 14 | return errors.Wrapf(GimmeDeepError(depth-1), "error happened on level %d", depth) 15 | } 16 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-wrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cockroachdb/errors" 7 | ) 8 | 9 | func GimmeDeepError(depth int) error { 10 | if depth == 1 { 11 | return errors.New("ooops, an error on level 1") 12 | } 13 | return errors.Wrapf(GimmeDeepError(depth-1), "error happened on level %d", depth) 14 | } 15 | 16 | func main() { 17 | fmt.Printf("%+v", GimmeDeepError(5)) 18 | } 19 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap-with-msg/wrap_msg_and_stack.go: -------------------------------------------------------------------------------- 1 | //go:build pkg.msg.stack 2 | // +build pkg.msg.stack 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func GimmeDeepError(depth int) error { 11 | if depth == 1 { 12 | return errors.New("ooops, an error on level 1") 13 | } 14 | return errors.WithMessagef(GimmeDeepError(depth-1), "error happened on level %d", depth) 15 | } 16 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/std-no-pointers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "syscall" 8 | ) 9 | 10 | func main() { 11 | e1 := url.InvalidHostError("www.golang-ninja.ru") 12 | e2 := url.InvalidHostError("www.golang-ninja.ru") 13 | fmt.Println(errors.Is(e1, e2)) // true 14 | 15 | e3 := syscall.Errno(0xd) 16 | e4 := syscall.Errno(0xd) 17 | fmt.Println(errors.Is(e3, e4)) // true 18 | } 19 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/hashing-pipeline/hash.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | ) 7 | 8 | type Hashable interface { 9 | Hash() Hash 10 | } 11 | 12 | type Hash []byte 13 | 14 | func (h Hash) String() string { 15 | return hex.EncodeToString(h) 16 | } 17 | 18 | func newHash(data []byte) Hash { 19 | h1 := sha256.Sum256(data) 20 | h2 := sha256.Sum256(h1[:]) 21 | return h2[:] 22 | } 23 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-is-via-errors-as/pipeline_err.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type PipelineError struct { 8 | User string 9 | Name string 10 | FailedSteps []string 11 | } 12 | 13 | func (p *PipelineError) Error() string { 14 | return fmt.Sprintf("pipeline %q error", p.Name) 15 | } 16 | 17 | func IsPipelineError(err error, user, pipelineName string) bool { 18 | // Реализуй меня. 19 | return false 20 | } 21 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap-with-msg/wrap_msg_only.go: -------------------------------------------------------------------------------- 1 | //go:build pkg.msg.only 2 | // +build pkg.msg.only 3 | 4 | package main 5 | 6 | import ( 7 | stderrors "errors" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func GimmeDeepError(depth int) error { 13 | if depth == 1 { 14 | return stderrors.New("ooops, an error on level 1") 15 | } 16 | return errors.WithMessagef(GimmeDeepError(depth-1), "error happened on level %d", depth) 17 | } 18 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-vs-fmt-4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func main() { 10 | var err error // nil error 11 | 12 | { 13 | err := fmt.Errorf("do operation: %w", err) 14 | fmt.Println(err, "|", err == nil) // do operation: %!w() | false 15 | } 16 | 17 | { 18 | err := errors.Wrap(err, "do operation") 19 | fmt.Println(err, "|", err == nil) // | true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/api-borders/01_db_user_own_err.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | var ErrNotFound = errors.New("obj not found") 11 | 12 | func GetUserByIDOwnError(ctx context.Context, uid UserID) (*User, error) { 13 | if err := selectUser(); err == sql.ErrNoRows { 14 | return nil, fmt.Errorf("exec query: %w: %v", ErrNotFound, err) 15 | } 16 | return &User{ID: "42"}, nil 17 | } 18 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/copy-file/v2.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | func CopyFileV2(src, dst string) error { 9 | r, err := os.Open(src) 10 | if err != nil { 11 | return err 12 | } 13 | defer r.Close() 14 | 15 | w, err := os.Create(dst) 16 | if err != nil { 17 | return err 18 | } 19 | defer w.Close() 20 | 21 | if _, err := io.Copy(w, r); err != nil { 22 | return err 23 | } 24 | 25 | return w.Sync() 26 | } 27 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-is-any/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "github.com/cockroachdb/errors" 10 | ) 11 | 12 | func main() { 13 | errs := []error{ 14 | context.Canceled, 15 | io.EOF, 16 | os.ErrClosed, 17 | os.ErrNotExist, 18 | errors.New("unknown error"), 19 | } 20 | 21 | for _, err := range errs { 22 | if errors.IsAny(err, errs...) { 23 | fmt.Println("gotcha!") 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/api-borders/01_db_user_own_private_err.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "fmt" 8 | ) 9 | 10 | var errNotFound = errors.New("obj not found") 11 | 12 | func GetUserByIDOwnPrivateError(ctx context.Context, uid UserID) (*User, error) { 13 | if err := selectUser(); err == sql.ErrNoRows { 14 | return nil, fmt.Errorf("exec query: %w: %v", errNotFound, err) 15 | } 16 | return &User{ID: "42"}, nil 17 | } 18 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/template-err-opaque/tmpl.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | // IsFunctionNotDefinedError говорит, является ли err ошибкой неопределённой в шаблоне функции. 4 | func IsFunctionNotDefinedError(err error) bool { 5 | return false 6 | } 7 | 8 | // IsExecUnexportedFieldError говорит, является ли err template.ExecError, 9 | // а именно ошибкой использования неэкспортируемого поля структуры. 10 | func IsExecUnexportedFieldError(err error) bool { 11 | return false 12 | } 13 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/errorf-1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | func main() { 11 | err := fmt.Errorf("cannot do operation: %w with %w", io.EOF, context.Canceled) 12 | fmt.Println(err) // cannot do operation: EOF with %!w(*errors.errorString=&{context canceled}) 13 | fmt.Println(errors.Is(err, io.EOF)) // false 14 | fmt.Println(errors.Is(err, context.Canceled)) // false 15 | } 16 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/error-printing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | var КонецФайла = errors.New("конец файла") //nolint:asciicheck,errname,revive,stylecheck 10 | 11 | func main() { 12 | verbs := []string{"%s", "%q", "%+q", "%v", "%#v"} 13 | 14 | for _, err := range []error{КонецФайла, io.EOF} { 15 | for _, f := range verbs { 16 | fmt.Printf("%3s - \t"+f, f, err) 17 | fmt.Println() 18 | } 19 | fmt.Println() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-new/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | // ErrGlobal экспортируемая переменная уровня пакета, 6 | // необходимая для предотвращений оптимизаций компилятора. 7 | var ErrGlobal error 8 | 9 | func BenchmarkGimmeError(b *testing.B) { 10 | for i := 0; i < b.N; i++ { 11 | ErrGlobal = GimmeError() 12 | } 13 | } 14 | 15 | func BenchmarkGimmePkgError(b *testing.B) { 16 | for i := 0; i < b.N; i++ { 17 | ErrGlobal = GimmePkgError() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/safe-factorial/factorial.go: -------------------------------------------------------------------------------- 1 | package factorial 2 | 3 | const maxDepth = 256 4 | 5 | // Реализуй нас. 6 | var ( 7 | ErrNegativeN error 8 | ErrTooDeep error 9 | ) 10 | 11 | // Calculate рекурсивно считает факториал входного числа n. 12 | // Если число меньше нуля, то возвращается ошибка ErrNegativeN. 13 | // Если для вычисления факториала потребуется больше maxDepth фреймов, то Calculate вернёт ErrTooDeep. 14 | func Calculate(n int) (int, error) { 15 | // Реализуй меня. 16 | return 0, nil 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Искусство работы с ошибками в Go (Golang) 2 | 3 | ### Полезные команды 4 | 5 | ``` 6 | $ task fmt 7 | $ task lint:dir -- "tasks/04-non-standard-modules/combine-errors" 8 | ``` 9 | 10 | ### Авторы 11 | - [Антон Телышев](https://github.com/Antonboom) 12 | - [Дмитрий Назарков](https://github.com/MysterySuperhero) 13 | 14 | --- 15 | 16 | Чат курса: https://t.me/golangninja
17 | Больше курсов: [https://golang-ninja.ru](https://golang-ninja.ru/?utm_source=github-error-handling&utm_medium=social&utm_campaign=general) 18 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-logging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | 6 | emperrors "emperror.dev/errors" 7 | logrushandler "emperror.dev/handler/logrus" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func main() { 12 | logger := logrus.New() 13 | handler := logrushandler.New(logger) 14 | 15 | err := errors.New("an error") 16 | handler.Handle(err) 17 | 18 | err2 := emperrors.WithDetails(err, "userID", 3587, "requestID", "4cfdc2e157eefe6facb983b1d557b3a1") 19 | handler.Handle(err2) 20 | } 21 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-with-message/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | var errInternal = stderrors.New("ooops, an error on level 1") 11 | 12 | func GimmeDeepError(depth int) error { 13 | if depth == 1 { 14 | return errors.WithStack(errInternal) 15 | } 16 | return errors.WithMessagef(GimmeDeepError(depth-1), "error happened on level %d", depth) 17 | } 18 | 19 | func main() { 20 | fmt.Printf("%+v", GimmeDeepError(5)) 21 | } 22 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/api-borders/0_db_user_original.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | ) 8 | 9 | type UserID string 10 | 11 | type User struct { 12 | ID UserID 13 | } 14 | 15 | func GetUserByIDOriginal(ctx context.Context, uid UserID) (*User, error) { 16 | if err := selectUser(); err == sql.ErrNoRows { 17 | return nil, fmt.Errorf("exec query: %w", err) 18 | } 19 | return &User{ID: "42"}, nil 20 | } 21 | 22 | func selectUser() error { 23 | return sql.ErrNoRows 24 | } 25 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/error-embedding/err_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestErrors(t *testing.T) { 10 | assert.EqualError(t, ErrAlreadyDone, "job is already done") 11 | assert.EqualError(t, ErrInconsistentData, "job payload is corrupted") 12 | assert.EqualError(t, ErrInvalidID, "invalid job id") 13 | assert.EqualError(t, ErrNotFound, "job wasn't found") 14 | assert.EqualError(t, ErrNotReady, "job is not ready to be performed") 15 | } 16 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-mark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/cockroachdb/errors" 8 | ) 9 | 10 | func main() { 11 | err := io.ErrUnexpectedEOF 12 | err = errors.Mark(err, io.EOF) // Помечаем ошибку как io.EOF. 13 | err = errors.Wrap(err, "something other happened") 14 | 15 | if errors.Is(err, io.EOF) { // true 16 | fmt.Println("error is io.EOF") 17 | } 18 | 19 | if errors.Is(err, io.ErrUnexpectedEOF) { // true 20 | fmt.Println("error is io.ErrUnexpectedEOF") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/error-embedding/err.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | var ( 4 | ErrAlreadyDone error = &AlreadyDoneError{Err{"job is already done"}} 5 | ErrInconsistentData error = &InconsistentDataError{Err{"job payload is corrupted"}} 6 | ErrInvalidID error = &InvalidIDError{Err{"invalid job id"}} 7 | ErrNotReady error = &NotReadyError{Err{"job is not ready to be performed"}} 8 | ErrNotFound error = &NotFoundError{Err{"job wasn't found"}} 9 | ) 10 | 11 | // Реализуй тип Err и типы для ошибок выше, используя его. 12 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/gotcha-func-call/main.go: -------------------------------------------------------------------------------- 1 | //nolint:govet 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func Handle() error { 11 | if err := usefulWork; err != nil { 12 | return fmt.Errorf("%w: %v", echo.ErrInternalServerError, err) 13 | } 14 | return nil 15 | } 16 | 17 | func usefulWork() error { 18 | return nil 19 | } 20 | 21 | func main() { 22 | if err := Handle(); err != nil { 23 | fmt.Println("handle err:", err) 24 | } else { 25 | fmt.Println("no handle err") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/ruleguard-wrap/gorules/rules.go: -------------------------------------------------------------------------------- 1 | package gorules 2 | 3 | import "github.com/quasilyte/go-ruleguard/dsl" 4 | 5 | // wrapWithPkgErrors - правило, которое предлагает врапить ошибки через github.com/pkg/errors. 6 | func wrapWithPkgErrors(m dsl.Matcher) { 7 | m.Import("github.com/pkg/errors") 8 | 9 | m.Match(`if err := $x; err != nil { return err }`). 10 | Report(`err is not wrapped`). 11 | Suggest(`if err := $x; err != nil { return errors.Wrap($x, "FIXME: wrap the error") }`) 12 | 13 | // Прочие варианты. 14 | // ... 15 | } 16 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-new/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func GimmeError() error { 11 | return stderrors.New("ooops, an error") // Просто возвращает ошибку с текстом. 12 | } 13 | 14 | func GimmePkgError() error { 15 | return errors.New("ooops, an error") // Возвращает ошибку вместе с текстом и стектрейсом. 16 | } 17 | 18 | func main() { 19 | fmt.Printf("%+v", GimmePkgError()) 20 | fmt.Println("\n---") 21 | fmt.Printf("%+v", GimmeError()) 22 | } 23 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/gotcha-err-iface-3/ops.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type OperationsErrors []error 8 | 9 | func (oe OperationsErrors) Error() string { 10 | return fmt.Sprintf("operations errors: %v", []error(oe)) 11 | } 12 | 13 | type IOperation interface { 14 | Do() error 15 | } 16 | 17 | func Handle(ops ...IOperation) error { 18 | var opsErrs OperationsErrors 19 | 20 | for _, op := range ops { 21 | if err := op.Do(); err != nil { 22 | opsErrs = append(opsErrs, err) 23 | } 24 | } 25 | 26 | return opsErrs 27 | } 28 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/status/status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "github.com/cockroachdb/errors" 5 | "github.com/cockroachdb/errors/extgrpc" 6 | "google.golang.org/grpc/codes" 7 | ) 8 | 9 | func Error(c codes.Code, msg string) error { 10 | return extgrpc.WrapWithGrpcCode(errors.New(msg), c) 11 | } 12 | 13 | func WrapErr(c codes.Code, msg string, err error) error { 14 | return extgrpc.WrapWithGrpcCode(errors.WrapWithDepth(1, err, msg), c) 15 | } 16 | 17 | func Code(err error) codes.Code { 18 | return extgrpc.GetGrpcCode(err) 19 | } 20 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/try/copy_file.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | // Несуществующий синтаксис. 4 | 5 | /* 6 | func CopyFile(src, dst string) (err error) { 7 | defer func() { 8 | if err != nil { 9 | err = fmt.Errorf("copy %q to %q: %v", src, dst, err) 10 | } 11 | }() 12 | 13 | r := try(os.Open(src)) 14 | defer r.Close() 15 | 16 | w := try(os.Create(dst)) 17 | defer func() { 18 | _ = w.Close() 19 | 20 | if err != nil { 21 | _ = os.Remove(dst) 22 | } 23 | }() 24 | 25 | try(io.Copy(w, r)) 26 | try(w.Close()) 27 | return nil 28 | } 29 | */ 30 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-vs-fmt-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | var ErrNotFound = stderrors.New("not found") 11 | 12 | func main() { 13 | { 14 | err := fmt.Errorf("index.html: %v", ErrNotFound) 15 | fmt.Println(err, "|", errors.Is(err, ErrNotFound)) // index.html: not found | false 16 | } 17 | 18 | { 19 | err := errors.Errorf("index.html: %v", ErrNotFound) 20 | fmt.Println(err, "|", errors.Is(err, ErrNotFound)) // index.html: not found | false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/gotcha-err-iface/main.go: -------------------------------------------------------------------------------- 1 | //nolint:staticcheck 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func Handle() error { 11 | var err *echo.HTTPError 12 | 13 | if err2 := usefulWork(); err2 != nil { 14 | err = echo.ErrInternalServerError 15 | } 16 | return err 17 | } 18 | 19 | func usefulWork() error { 20 | return nil 21 | } 22 | 23 | func main() { 24 | if err := Handle(); err != nil { 25 | fmt.Println("handle err:", err) 26 | } else { 27 | fmt.Println("no handle err") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-as/pipeline_err.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import "fmt" 4 | 5 | type UserError struct { 6 | Operation string 7 | User string 8 | } 9 | 10 | func (u *UserError) Error() string { 11 | return fmt.Sprintf("user %s cannot do op %s", u.User, u.Operation) 12 | } 13 | 14 | type PipelineError struct { 15 | User string 16 | Name string 17 | FailedSteps []string 18 | } 19 | 20 | func (p *PipelineError) Error() string { 21 | return fmt.Sprintf("pipeline %q error", p.Name) 22 | } 23 | 24 | // Добавь метод As для типа *PipelineError. 25 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/errorf-bench/errorf_bench_test.go: -------------------------------------------------------------------------------- 1 | package errorfbench 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | // ErrGlobal экспортируемая переменная уровня пакета, 10 | // необходимая для предотвращений оптимизаций компилятора. 11 | var ErrGlobal error 12 | 13 | func BenchmarkErrorsNew(b *testing.B) { 14 | for i := 0; i < b.N; i++ { 15 | ErrGlobal = errors.New("invalid token") 16 | } 17 | } 18 | 19 | func BenchmarkErrorf(b *testing.B) { 20 | for i := 0; i < b.N; i++ { 21 | ErrGlobal = fmt.Errorf("invalid token") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/error-types/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "io/fs" 8 | "net" 9 | "syscall" 10 | ) 11 | 12 | func main() { 13 | for _, e := range []error{ 14 | net.UnknownNetworkError("usp"), 15 | fmt.Errorf("cannot read from file: %v", io.EOF), 16 | io.EOF, 17 | syscall.EDOM, 18 | fmt.Errorf("cannot save HTML page: %v", 19 | fmt.Errorf("cannot fetch URL: %v", context.Canceled)), 20 | &fs.PathError{Op: "seek", Path: "/etc/hosts", Err: fs.ErrInvalid}, 21 | } { 22 | fmt.Printf("%-30T%q\n", e, e) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/from-error-to-error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | type PipelineError struct { 10 | User string 11 | Name string 12 | FailedSteps []string 13 | } 14 | 15 | func (p *PipelineError) Error() string { 16 | return fmt.Sprintf("pipeline %q error", p.Name) 17 | } 18 | 19 | func main() { 20 | var p error = &PipelineError{} 21 | // p := &PipelineError{} 22 | if errors.As(net.UnknownNetworkError("tdp"), &p) { 23 | fmt.Println(p) 24 | } else { 25 | fmt.Println("As() return false") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/cockroach-different-wraps/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | cdberrors "github.com/cockroachdb/errors" 8 | pkgerrors "github.com/pkg/errors" 9 | ) 10 | 11 | func main() { 12 | err := stderrors.New("an error") 13 | 14 | stdErr := fmt.Errorf("wrap: %w", err) 15 | fmt.Println(cdberrors.UnwrapOnce(stdErr)) 16 | 17 | pkgErr := pkgerrors.Wrap(err, "wrap 1") 18 | fmt.Println(cdberrors.UnwrapOnce(pkgErr)) 19 | 20 | cockroachErr := cdberrors.Wrap(err, "wrap 2") 21 | fmt.Println(cdberrors.UnwrapOnce(cockroachErr)) 22 | } 23 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/single-unwrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | type FileLoadError struct { 10 | URL string 11 | Err error // Для хранения "родительской" ошибки. 12 | } 13 | 14 | func (p *FileLoadError) Error() string { 15 | // Текст "родительской ошибки" фигурирует в тексте этой ошибки. 16 | return fmt.Sprintf("%q: %v", p.URL, p.Err) 17 | } 18 | 19 | func (p *FileLoadError) Unwrap() error { 20 | return p.Err 21 | } 22 | 23 | func main() { 24 | fmt.Println( 25 | errors.Unwrap(&FileLoadError{Err: context.Canceled})) 26 | } 27 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/equal-err-text/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/jackc/pgx" 11 | ) 12 | 13 | func main() { 14 | exist := errors.New("file already exists") 15 | fmt.Println(errors.Is(exist, os.ErrExist)) // false 16 | 17 | eof := errors.New("EOF") 18 | fmt.Println(errors.Is(eof, io.EOF)) // false 19 | 20 | noRows := errors.New("no rows in result set") 21 | fmt.Println(errors.Is(noRows, pgx.ErrNoRows)) // false 22 | 23 | fmt.Println(errors.Is(sql.ErrNoRows, pgx.ErrNoRows)) // false 24 | } 25 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-multiple-errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | func main() { 12 | var eg errgroup.Group 13 | 14 | eg.Go(func() error { 15 | return errors.New("first error") 16 | }) 17 | 18 | eg.Go(func() error { 19 | time.Sleep(time.Second) 20 | return errors.New("second error") 21 | }) 22 | 23 | eg.Go(func() error { 24 | time.Sleep(2 * time.Second) 25 | return errors.New("third error") 26 | }) 27 | 28 | fmt.Println(eg.Wait()) // first error 29 | } 30 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/hashicorp-multierror-gotcha-1/main.go: -------------------------------------------------------------------------------- 1 | //nolint:staticcheck 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | func main() { 11 | if err := collectErrors(); err != nil { 12 | fmt.Println("errors!") 13 | } else { 14 | fmt.Println("ok") 15 | } 16 | } 17 | 18 | func collectErrors() error { 19 | var err error 20 | err = multierror.Append(err, foo()) 21 | err = multierror.Append(err, bar()) 22 | return err 23 | } 24 | 25 | func foo() error { 26 | return nil 27 | } 28 | 29 | func bar() error { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-panic-and-recover/client-server/server/middleware.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "emperror.dev/emperror" 8 | ) 9 | 10 | func NewPanicMiddleware(h http.Handler) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 12 | // Recover паники и обработка её как ошибки. 13 | defer emperror.HandleRecover(emperror.ErrorHandlerFunc(func(err error) { 14 | log.Println("panic handler called:", err) 15 | internalServerError(w, "panic happened") 16 | })) 17 | h.ServeHTTP(w, req) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/resp-body-close/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | res, err := http.Get("http://www.golang-ninja.ru/") //nolint:noctx 11 | if err != nil { 12 | panic(err) 13 | } 14 | defer res.Body.Close() 15 | /* 16 | defer func() { 17 | if err := res.Body.Close(); err != nil { 18 | log.Println("cannot close response body: " + err.Err()) 19 | } 20 | }() 21 | */ 22 | 23 | index, err := io.ReadAll(res.Body) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | fmt.Println(string(index)[:300]) 29 | } 30 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-factory/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewError(t *testing.T) { 10 | for _, s := range []string{ 11 | "context canceled", 12 | "end of file", 13 | "invalid token", 14 | } { 15 | err := NewError(s) 16 | assert.Equal(t, s, err.Error()) 17 | } 18 | } 19 | 20 | func TestNewErrorEquality(t *testing.T) { 21 | lhs := NewError("invalid token") 22 | rhs := NewError("invalid token") 23 | assert.False(t, lhs == rhs, "different errors with the same text must not be equal") 24 | } 25 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-problem/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/constant-errors-problem/dirtyhacker" 8 | ) 9 | 10 | func main() { 11 | err1 := io.EOF 12 | err2 := io.EOF 13 | fmt.Println(io.EOF) // EOF 14 | fmt.Println(err1 == err2) // true 15 | 16 | // Меняем глобальный io.EOF, 17 | // в принципе это можно сделать в init пакета dirtyhacker. 18 | dirtyhacker.MutateEOF() 19 | 20 | fmt.Println(io.EOF) // nil 21 | fmt.Println(err1 == io.EOF) // false 22 | } 23 | -------------------------------------------------------------------------------- /examples/06-working-with-errors-in-tests/strconv-errors/main.go: -------------------------------------------------------------------------------- 1 | //nolint:staticcheck 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | func main() { 11 | // strconv.ParseInt: parsing "1": invalid base 123 12 | _, err := strconv.ParseInt("1", 123, 32) 13 | fmt.Println(err) 14 | 15 | var numErr *strconv.NumError 16 | if errors.As(err, &numErr) { 17 | fmt.Printf("%v (%T)\n", numErr.Err, numErr.Err) // invalid base 123 (*errors.errorString) 18 | } 19 | 20 | // strconv.ParseInt: parsing "1": invalid bit size -1 21 | _, err = strconv.ParseInt("1", 10, -1) 22 | fmt.Println(err) 23 | } 24 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/get-index-from-filename/index_bench_test.go: -------------------------------------------------------------------------------- 1 | package index 2 | 3 | import "testing" 4 | 5 | func BenchmarkGetIndexFromFileName(b *testing.B) { 6 | cases := []string{ 7 | "parsed_page", 8 | "parsedpage", 9 | "parsed_page_", 10 | "parsed_page_100_suffix", 11 | "parsed_page_-1", 12 | "parsed_page_0", 13 | "parsed_page_1", 14 | "parsed_page_15.5", 15 | "parsed_page_1000", 16 | "absolutely incorrect file name", 17 | } 18 | 19 | b.ResetTimer() 20 | for i := 0; i < b.N; i++ { 21 | for i := range cases { 22 | _, _ = GetIndexFromFileName(cases[i]) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-structure-filling/get_order.go: -------------------------------------------------------------------------------- 1 | package orders 2 | 3 | import ( 4 | "context" 5 | 6 | "golang.org/x/sync/errgroup" 7 | ) 8 | 9 | type Order struct { 10 | BuyerName string 11 | BuyerAddress string 12 | SellerName string 13 | } 14 | 15 | type StringValueGetter func(ctx context.Context) (string, error) 16 | 17 | func GetOrder(ctx context.Context, getBuyerName, getBuyerAddress, getSellerName StringValueGetter) (Order, error) { 18 | // Для сохранения импортов. Удали эту строку. 19 | _ = errgroup.Group{} 20 | 21 | // Реализуй меня. 22 | return Order{}, nil 23 | } 24 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "allocator.h" 5 | 6 | int main() 7 | { 8 | size_t size = 0; 9 | int uid = 0; 10 | 11 | errno = 0; 12 | if (scanf("%d %zu", &uid, &size) != 2) { 13 | perror("scanf failed"); 14 | exit(1); 15 | } 16 | 17 | errno = 0; 18 | void *p = allocate(uid, size); 19 | if (p == NULL) { 20 | printf("allocation error: %s\n", strerror(errno)); 21 | } else { 22 | printf("allocation was successful for %zu bytes\n", size); 23 | free(p); 24 | } 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/validation-errors/search_request.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | import ( 4 | // Доступные пакеты, _ для сохранения импортов. 5 | _ "errors" 6 | _ "fmt" 7 | _ "regexp" 8 | _ "strings" 9 | ) 10 | 11 | const maxPageSize = 100 12 | 13 | // Реализуй нас. 14 | var ( 15 | errIsNotRegexp error 16 | errInvalidPage error 17 | errInvalidPageSize error 18 | ) 19 | 20 | // Реализуй мои методы. 21 | type ValidationErrors []error 22 | 23 | type SearchRequest struct { 24 | Exp string 25 | Page int 26 | PageSize int 27 | } 28 | 29 | func (r SearchRequest) Validate() error { 30 | // Реализуй меня. 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/copy-file/v3_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestCopyFileV3(t *testing.T) { 12 | dst := filepath.Join(os.TempDir(), "TestCopyFileV3") 13 | 14 | err := CopyFileV3("/etc/hosts", dst) 15 | require.NoError(t, err) 16 | 17 | defer func() { 18 | require.NoError(t, os.Remove(dst)) 19 | }() 20 | 21 | { 22 | lhs, err := os.Stat("/etc/hosts") 23 | require.NoError(t, err) 24 | 25 | rhs, err := os.Stat(dst) 26 | require.NoError(t, err) 27 | 28 | require.Equal(t, lhs.Size(), rhs.Size()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/opaque-errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | func IsTemporary(err error) bool { 9 | // Приводим ошибку к интерфейсу, тем самым проверяя поведение. 10 | e, ok := err.(interface{ Temporary() bool }) 11 | return ok && e.Temporary() 12 | } 13 | 14 | func networkRequest() error { 15 | return &net.DNSError{ // У *DNSError есть метод Temporary(), загляните внутрь. 16 | IsTimeout: true, 17 | IsTemporary: true, 18 | } 19 | } 20 | 21 | func main() { 22 | if err := networkRequest(); err != nil { 23 | fmt.Println("error is temporary:", IsTemporary(err)) // error is temporary: true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/handmade-stacktrace/stack_trace.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | const maxStacktraceDepth = 32 4 | 5 | type Frame uintptr 6 | 7 | func (f Frame) pc() uintptr { 8 | return uintptr(f) - 1 9 | } 10 | 11 | func (f Frame) String() string { 12 | // Реализуй меня. 13 | return "" 14 | } 15 | 16 | type StackTrace []Frame 17 | 18 | func (s StackTrace) String() string { 19 | // Реализуй меня. 20 | return "" 21 | } 22 | 23 | // Trace возвращает стектрейс глубиной не более maxStacktraceDepth. 24 | // Возвращаемый стектрейс начинается с того места, где была вызвана Trace. 25 | func Trace() StackTrace { 26 | // Реализуй меня. 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/json-writer/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "net/http" 4 | 5 | type ILogger interface { 6 | Error(msg string) 7 | } 8 | 9 | type IDataProvider interface { 10 | Data() any 11 | } 12 | 13 | type Server struct { 14 | log ILogger 15 | provider IDataProvider 16 | } 17 | 18 | func New(l ILogger, d IDataProvider) *Server { 19 | return &Server{log: l, provider: d} 20 | } 21 | 22 | func (s *Server) HandleIndex(w http.ResponseWriter, _ *http.Request) { 23 | s.newJSONWriter(w).Write(s.provider.Data()) 24 | } 25 | 26 | func (s *Server) newJSONWriter(w http.ResponseWriter) jsonWriter { 27 | return jsonWriter{w: w, log: s.log} 28 | } 29 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/not-found-error-constructor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type HTTPError interface { 9 | error 10 | StatusCode() int 11 | } 12 | 13 | type NotFoundError struct{} 14 | 15 | func (n *NotFoundError) Error() string { 16 | return "Not Found" 17 | } 18 | 19 | func (n NotFoundError) StatusCode() int { 20 | return http.StatusNotFound 21 | } 22 | 23 | func NewNotFoundError() (*NotFoundError, error) { 24 | return new(NotFoundError), nil 25 | } 26 | 27 | func main() { 28 | var httpErr HTTPError 29 | httpErr, _ = NewNotFoundError() 30 | fmt.Println(httpErr.StatusCode(), httpErr.Error()) // 404 Not Found 31 | } 32 | -------------------------------------------------------------------------------- /examples/02-c-errors-concept/custom_err_value.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef int errno_t; 7 | 8 | const errno_t ESOMETHINGREALLYBAD = 4242; 9 | 10 | errno_t g() 11 | { 12 | // ... 13 | int something_really_bad_happens = 1; 14 | 15 | // ... 16 | if (something_really_bad_happens == 1) { 17 | return ESOMETHINGREALLYBAD; 18 | } 19 | 20 | // ... 21 | return 0; 22 | } 23 | 24 | int main() 25 | { 26 | errno_t err = g(); 27 | if (err != 0) { 28 | puts(strerror(err)); // Unknown error: 4242 29 | return EXIT_FAILURE; 30 | } 31 | 32 | return EXIT_SUCCESS; 33 | } 34 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/concurrent-event-publishing/job_test.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestJob_Handle(t *testing.T) { 12 | ctrl := gomock.NewController(t) 13 | defer ctrl.Finish() 14 | 15 | eventStreamMock := NewMockeventStream(ctrl) 16 | job := &Job{eventStream: eventStreamMock} 17 | 18 | eventStreamMock.EXPECT().Publish(gomock.Any(), "user-id-1", "some event") 19 | eventStreamMock.EXPECT().Publish(gomock.Any(), "user-id-2", "another yet event") 20 | 21 | err := job.Handle(context.Background(), `{}`) 22 | require.NoError(t, err) 23 | } 24 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/shadowed-err-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func Handle() (err error) { 9 | if err = handleConn(); err != nil { 10 | // Новая ошибка не перетирает возвращаемую. 11 | // Попробуйте заменить `:=` на `=`. 12 | if err := closeConn(); err != nil { 13 | log.Println(err) 14 | } 15 | } 16 | return 17 | } 18 | 19 | func handleConn() error { 20 | return fmt.Errorf("handle conn error") 21 | } 22 | 23 | func closeConn() error { 24 | return fmt.Errorf("close error") 25 | } 26 | 27 | func main() { 28 | fmt.Println(Handle()) 29 | } 30 | 31 | /* 32 | 2021/07/10 11:58:53 close error 33 | handle conn error 34 | */ 35 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-error-types/errors.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type AlreadyDoneError struct{} 4 | 5 | func (e *AlreadyDoneError) Error() string { return "job is already done" } 6 | 7 | type InconsistentDataError struct{} 8 | 9 | func (e *InconsistentDataError) Error() string { return "job payload is corrupted" } 10 | 11 | type InvalidIDError struct{} 12 | 13 | func (e *InvalidIDError) Error() string { return "invalid job id" } 14 | 15 | type NotFoundError struct{} 16 | 17 | func (e *NotFoundError) Error() string { return "job wasn't found" } 18 | 19 | type NotReadyError struct{} 20 | 21 | func (e *NotReadyError) Error() string { return "job is not ready to be performed" } 22 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/shadowed-err-1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ErrInvalidUserID = errors.New("invalid user id") 9 | 10 | type UserID string 11 | 12 | type User struct { 13 | ID UserID 14 | } 15 | 16 | //nolint:typecheck 17 | func SaveUser(u User) (err error) { 18 | if !isValidID(u.ID) { 19 | err := fmt.Errorf("%w: %v", ErrInvalidUserID, u.ID) // err is shadowed during return 20 | return 21 | } 22 | 23 | return saveUser(u) 24 | } 25 | 26 | func isValidID(id UserID) bool { 27 | return false 28 | } 29 | 30 | func saveUser(u User) error { 31 | return nil 32 | } 33 | 34 | func main() { 35 | fmt.Println(SaveUser(User{ID: "XXX"})) 36 | } 37 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/parse-token-for-security/jwt_errors.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrEmptyJWT = errors.New("empty jwt data") 7 | ErrInvalidTokenFormat = errors.New("invalid token format: 'header.payload.signature' was expected") 8 | ErrInvalidHeaderEncoding = errors.New("invalid header encoding") 9 | ErrUnsupportedTokenType = errors.New("unsupported token type") 10 | ErrUnsupportedSigningAlgo = errors.New("unsupported the signing algo") 11 | ErrInvalidSignature = errors.New("invalid signature") 12 | ErrInvalidPayloadEncoding = errors.New("invalid payload encoding") 13 | ErrExpiredToken = errors.New("token was expired") 14 | ) 15 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-if/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/cockroachdb/errors" 8 | ) 9 | 10 | type isTemporary interface { 11 | Temporary() bool 12 | } 13 | 14 | func IsTemporary(err error) (any, bool) { 15 | e, ok := err.(isTemporary) 16 | return e, ok && e.Temporary() // Возвращаем не только флаг, но и ошибку. 17 | } 18 | 19 | func main() { 20 | dnsErr := &net.DNSError{IsTemporary: true} // Произошла сетевая ошибка. 21 | 22 | err := fmt.Errorf("second wrap: %w", 23 | fmt.Errorf("first wrap: %w", dnsErr)) 24 | 25 | e, ok := errors.If(err, IsTemporary) 26 | fmt.Printf("%T is temporary: %t", e, ok) // *net.DNSError is temporary: true 27 | } 28 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/gotcha-err-iface-1/http_error.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | var ErrInternalServerError = NewHTTPError(http.StatusInternalServerError) 9 | 10 | // HTTPError represents an error that occurred while handling a request. 11 | type HTTPError struct { 12 | Code int 13 | Message string 14 | } 15 | 16 | // NewHTTPError creates a new HTTPError instance. 17 | func NewHTTPError(code int) *HTTPError { 18 | return &HTTPError{Code: code, Message: http.StatusText(code)} 19 | } 20 | 21 | // Error makes it compatible with `error` interface. 22 | func (he *HTTPError) Error() string { 23 | return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) 24 | } 25 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/hashing-pipeline/hash_tree.go: -------------------------------------------------------------------------------- 1 | package pipeline 2 | 3 | import "errors" 4 | 5 | var errNothingToHash = errors.New("nothing to hash") 6 | 7 | // CalculateHash реализует хеширование входящих элементов по принципу дерева хешей (Merkle tree). 8 | // Если входящий слайс пуст, то возвращает ошибку errNothingToHash. 9 | func CalculateHash(hh []Hashable) (Hash, error) { 10 | // Реализуй меня. При желании воспользуйся node. 11 | return nil, nil 12 | } 13 | 14 | // node представляет собой узел хеш-дерева. 15 | type node struct { 16 | left Hashable 17 | right Hashable 18 | } 19 | 20 | func (n node) Hash() Hash { 21 | return newHash(append(n.left.Hash(), n.right.Hash()...)) 22 | } 23 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/byte-reader-byte-writer/byte_buffer.go: -------------------------------------------------------------------------------- 1 | package bytebuffer 2 | 3 | const bufferMaxSize = 1024 4 | 5 | type MaxSizeExceededError struct { 6 | desiredLen int 7 | } 8 | 9 | type EndOfBufferError struct{} 10 | 11 | type ByteBuffer struct { 12 | buffer []byte 13 | offset int 14 | } 15 | 16 | // Необходимо сделать так, чтобы тип *ByteBuffer реализовывал интерфейсы io.ByteWriter и io.ByteReader. 17 | // 18 | // Метод WriteByte должен возвращать ошибку *MaxSizeExceededError при попытке записи в буфер, 19 | // если в нём уже больше bufferMaxSize байт. 20 | // 21 | // Метод ReadByte должен возвращать ошибку *EndOfBufferError при попытке чтения из буфера, 22 | // если ранее буфер уже был вычитан полностью. 23 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-vs-fmt-1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | var ErrNotFound = stderrors.New("not found") 11 | 12 | func main() { 13 | { 14 | err := fmt.Errorf("%w: index.html", ErrNotFound) 15 | fmt.Println(err) // not found: index.html 16 | 17 | err = fmt.Errorf("in the middle: %w: index.html", ErrNotFound) 18 | fmt.Println(err) // in the middle: not found: index.html 19 | 20 | err = fmt.Errorf("index.html: %w", ErrNotFound) 21 | fmt.Println(err) // index.html: not found 22 | } 23 | 24 | { 25 | err := errors.Wrap(ErrNotFound, "index.html") 26 | fmt.Println(err) // index.html: not found 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-ctx-cancelling-issue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | func main() { 11 | ctx := context.TODO() 12 | action(ctx, "initial action") 13 | 14 | g, ctx := errgroup.WithContext(ctx) 15 | 16 | g.Go(func() error { 17 | action(ctx, "concurrent action 1") 18 | return nil 19 | }) 20 | g.Go(func() error { 21 | action(ctx, "concurrent action 2") 22 | return nil 23 | }) 24 | 25 | _ = g.Wait() 26 | action(ctx, "final action") 27 | } 28 | 29 | func action(ctx context.Context, s string) { 30 | if ctx.Err() != nil { 31 | fmt.Println(ctx.Err()) 32 | return 33 | } 34 | fmt.Println(s) 35 | } 36 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/idempotent-http-server-listen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var s http.Server 11 | 12 | go func() { 13 | time.Sleep(time.Second * 3) 14 | 15 | if err := s.Close(); err != nil { 16 | panic(err) 17 | } 18 | 19 | for i := 0; i < 5; i++ { 20 | if err := s.ListenAndServe(); err != nil { 21 | fmt.Println(err) 22 | } 23 | } 24 | }() 25 | 26 | if err := s.ListenAndServe(); err != nil { 27 | fmt.Println(err) 28 | } 29 | 30 | /* 31 | http: Server closed 32 | http: Server closed 33 | http: Server closed 34 | http: Server closed 35 | http: Server closed 36 | http: Server closed 37 | */ 38 | } 39 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | // go test -tags std -benchmem -bench . > std.txt 9 | // go test -tags pkg -benchmem -bench . > pkg.txt 10 | // benchstat -alpha 1.1 std.txt pkg.txt 11 | 12 | // ErrGlobal экспортируемая переменная уровня пакета, 13 | // необходимая для предотвращений оптимизаций компилятора. 14 | var ErrGlobal error 15 | 16 | var depths = []int{1, 2, 4, 8, 16, 32} 17 | 18 | func BenchmarkGimmeDeepError(b *testing.B) { 19 | for _, depth := range depths { 20 | b.Run(strconv.Itoa(depth), func(b *testing.B) { 21 | for i := 0; i < b.N; i++ { 22 | ErrGlobal = GimmeDeepError(depth) 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/uber-multierr-append-invoke-defer-gotcha/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "go.uber.org/multierr" 8 | ) 9 | 10 | func main() { 11 | err := good() 12 | fmt.Printf("good: %v\n", err) // good: unexpected EOF 13 | 14 | err2 := bad() 15 | fmt.Printf("bad: %v\n", err2) // bad: , хотя должен вывести "unexpected EOF" 16 | } 17 | 18 | func good() (err error) { 19 | c := new(CloserMock) 20 | defer multierr.AppendInvoke(&err, multierr.Close(c)) 21 | 22 | c.SetErr(io.ErrUnexpectedEOF) 23 | return nil 24 | } 25 | 26 | func bad() (err error) { 27 | c := new(CloserMock) 28 | defer multierr.AppendInto(&err, c.Close()) 29 | 30 | c.SetErr(io.ErrUnexpectedEOF) 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-expensive-stack/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | // go test -tags pkg -benchmem -bench . > pkg.txt 9 | // go test -tags cockroach -benchmem -bench . > cdb.txt 10 | // benchstat -alpha 1.1 pkg.txt cdb.txt 11 | 12 | // ErrGlobal экспортируемая переменная уровня пакета, 13 | // необходимая для предотвращений оптимизаций компилятора. 14 | var ErrGlobal error 15 | 16 | var depths = []int{1, 2, 4, 8, 16, 32} 17 | 18 | func BenchmarkGimmeDeepError(b *testing.B) { 19 | for _, depth := range depths { 20 | b.Run(strconv.Itoa(depth), func(b *testing.B) { 21 | for i := 0; i < b.N; i++ { 22 | ErrGlobal = GimmeDeepError(depth) 23 | } 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/get_user_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef GET_USER_HANDLER_H 2 | #define GET_USER_HANDLER_H 3 | 4 | #include 5 | #include "db.h" 6 | #include "marshalers.h" 7 | 8 | typedef enum { 9 | HTTP_ERR_OK = 0, // 200 10 | // Реализуй нас. 11 | // ... 12 | } http_error_t; 13 | 14 | const char* const HTTP_ERR_STRS[] = { 15 | "200 OK", 16 | // Реализуй нас. 17 | // ... 18 | }; 19 | 20 | const char *http_error_str(http_error_t err) 21 | { 22 | return HTTP_ERR_STRS[err]; 23 | } 24 | 25 | http_error_t get_user_handler(char *request_data, char **response_data) 26 | { 27 | http_error_t err = HTTP_ERR_OK; 28 | // Реализуй меня. 29 | // ... 30 | return err; 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/json-writer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/golang-ninja-courses/error-handling-mastery/tasks/05-errors-best-practices/json-writer/server" 8 | ) 9 | 10 | func main() { 11 | s := server.New(stdLogger{}, simpleMsgProvider{}) 12 | http.HandleFunc("/", s.HandleIndex) 13 | 14 | if err := http.ListenAndServe(":8080", http.DefaultServeMux); err != nil { 15 | log.Fatal(err) 16 | } 17 | } 18 | 19 | type stdLogger struct{} 20 | 21 | func (l stdLogger) Error(msg string) { 22 | log.Println(msg) 23 | } 24 | 25 | type simpleMsgProvider struct{} 26 | 27 | func (s simpleMsgProvider) Data() any { 28 | return struct { 29 | Msg string `json:"msg"` 30 | }{Msg: "OK"} 31 | } 32 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/idempotent-bufio-writer-flush/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | f, err := os.Open("/etc/hosts") 11 | if err != nil { 12 | panic(err) 13 | } 14 | if err := f.Close(); err != nil { 15 | panic(err) 16 | } 17 | 18 | w := bufio.NewWriter(f) 19 | w.WriteString("bad") //nolint:errcheck 20 | 21 | for i := 0; i < 5; i++ { 22 | if err := w.Flush(); err != nil { 23 | fmt.Println(err) 24 | } 25 | } 26 | 27 | /* 28 | write /etc/hosts: file already closed 29 | write /etc/hosts: file already closed 30 | write /etc/hosts: file already closed 31 | write /etc/hosts: file already closed 32 | write /etc/hosts: file already closed 33 | */ 34 | } 35 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/mini-word/document.go: -------------------------------------------------------------------------------- 1 | package miniword 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | const maxPages = 3 8 | 9 | var ( 10 | errInvalidPageNumber error 11 | errNoMorePages error 12 | errEmptyText error 13 | ) 14 | 15 | type Document struct { // Реализуй меня. 16 | } 17 | 18 | func NewDocument() *Document { 19 | // Реализуй меня. 20 | return nil 21 | } 22 | 23 | func (d *Document) AddPage() { 24 | // Реализуй меня. 25 | } 26 | 27 | func (d *Document) SetActivePage(number int) { 28 | // Реализуй меня. 29 | } 30 | 31 | func (d *Document) WriteText(s string) { 32 | // Реализуй меня. 33 | } 34 | 35 | func (d *Document) WriteTo(w io.Writer) (n int64, err error) { 36 | // Реализуй меня. 37 | return 0, nil 38 | } 39 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-try-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | func main() { 11 | var eg errgroup.Group 12 | eg.SetLimit(1) 13 | 14 | ready := make(chan struct{}) 15 | eg.Go(func() error { 16 | defer close(ready) 17 | time.Sleep(3 * time.Second) 18 | return nil 19 | }) 20 | 21 | f := func() error { 22 | log.Println("SUCCESS") 23 | return nil 24 | } 25 | 26 | log.Println(eg.TryGo(f)) 27 | log.Println(eg.TryGo(f)) 28 | <-ready 29 | log.Println(eg.TryGo(f)) 30 | 31 | _ = eg.Wait() 32 | } 33 | 34 | /* 35 | 2023/06/04 12:19:57 false 36 | 2023/06/04 12:19:57 false 37 | 2023/06/04 12:20:00 true 38 | 2023/06/04 12:20:00 SUCCESS 39 | */ 40 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "get_user_handler.h" 5 | 6 | int main() 7 | { 8 | const int n = 100; 9 | char request_data[n]; 10 | 11 | if (fgets(request_data, n, stdin) == NULL) { 12 | return EXIT_FAILURE; 13 | } 14 | 15 | char *resp = NULL; 16 | http_error_t err = get_user_handler(request_data, &resp); 17 | 18 | puts(http_error_str(err)); 19 | 20 | if (err) { 21 | if (resp != NULL) { 22 | puts("Response is not NULL after http error!"); 23 | return EXIT_FAILURE; 24 | } 25 | 26 | } else { 27 | puts(resp); 28 | free(resp); 29 | } 30 | 31 | return EXIT_SUCCESS; 32 | } 33 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/users-sort/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | ) 7 | 8 | type User struct { 9 | ID string 10 | Email string 11 | } 12 | 13 | type ByEmail []User 14 | 15 | func (s ByEmail) Len() int { 16 | return len(s) 17 | } 18 | 19 | func (s ByEmail) Less(i, j int) bool { 20 | return s[i].Email < s[j].Email 21 | } 22 | 23 | func (s ByEmail) Swap(i, j int) { 24 | s[i], s[j] = s[j], s[i] 25 | } 26 | 27 | func main() { 28 | users := []User{ 29 | {ID: "1", Email: "bob@gmail.com"}, 30 | {ID: "2", Email: "alex@gmail.com"}, 31 | {ID: "2", Email: "alice@gmail.com"}, 32 | } 33 | 34 | sort.Sort(ByEmail(users)) 35 | 36 | // [{2 alex@gmail.com} {2 alice@gmail.com} {1 bob@gmail.com}] 37 | fmt.Println(users) 38 | } 39 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/hashicorp-multierror-group/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | var ( 11 | errNotFound = errors.New("not found") 12 | errUnauthorized = errors.New("unauthorized") 13 | errUnknown = errors.New("unknown error") 14 | ) 15 | 16 | func main() { 17 | var eg multierror.Group 18 | 19 | eg.Go(func() error { return errNotFound }) 20 | eg.Go(func() error { return errUnauthorized }) 21 | eg.Go(func() error { return errUnknown }) 22 | 23 | err := eg.Wait() 24 | fmt.Println(errors.Is(err, errNotFound)) // true 25 | fmt.Println(errors.Is(err, errUnauthorized)) // true 26 | fmt.Println(errors.Is(err, errUnknown)) // true 27 | } 28 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/hashicorp-multierror-gotcha-2/main.go: -------------------------------------------------------------------------------- 1 | //nolint:staticcheck 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | func main() { 11 | if err := collectErrors(); err != nil { 12 | fmt.Println("errors!") 13 | } else { 14 | fmt.Println("ok") 15 | } 16 | } 17 | 18 | func collectErrors() error { 19 | var mErr *multierror.Error 20 | 21 | mErr = multierror.Append(mErr, foo()) 22 | mErr = multierror.Append(mErr, bar()) 23 | 24 | if mErr != nil { 25 | mErr.ErrorFormat = func(errors []error) string { 26 | return fmt.Sprintf("%v", errors) 27 | } 28 | } 29 | 30 | return mErr 31 | } 32 | 33 | func foo() error { 34 | return nil 35 | } 36 | 37 | func bar() error { 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/check-handle/print_sum.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | func printSum(a, b string) error { 9 | x, err := strconv.Atoi(a) 10 | if err != nil { 11 | return fmt.Errorf("printSum(%q + %q): %v", a, b, err) 12 | } 13 | 14 | y, err := strconv.Atoi(b) 15 | if err != nil { 16 | return fmt.Errorf("printSum(%q + %q): %v", a, b, err) 17 | } 18 | 19 | fmt.Println("result:", x+y) 20 | return nil 21 | } 22 | 23 | // Несуществующий синтаксис. 24 | 25 | /* 26 | func printSum(a, b string) error { 27 | handle err { 28 | return fmt.Errorf("printSum(%q + %q): %v", a, b, err) 29 | } 30 | x := check strconv.Atoi(a) 31 | y := check strconv.Atoi(b) 32 | fmt.Println("result:", x + y) 33 | return nil 34 | } 35 | */ 36 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/hashicorp-multierror-gotcha-3/main.go: -------------------------------------------------------------------------------- 1 | //nolint:ineffassign,wastedassign 2 | package main 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/hashicorp/go-multierror" 9 | ) 10 | 11 | func main() { 12 | if err := collectErrors(); err != nil { 13 | fmt.Println("errors!", err) 14 | } else { 15 | fmt.Println("ok") 16 | } 17 | } 18 | 19 | func collectErrors() error { 20 | var err error 21 | 22 | if err := foo(); err != nil { 23 | err = multierror.Append(err, err) 24 | } 25 | 26 | if err := bar(); err != nil { 27 | err = multierror.Append(err, err) 28 | } 29 | 30 | return err 31 | } 32 | 33 | func foo() error { 34 | return errors.New("error from foo") 35 | } 36 | 37 | func bar() error { 38 | return errors.New("error from bar") 39 | } 40 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/wrap-nil/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestWrapf(t *testing.T) { 12 | t.Run("positive", func(t *testing.T) { 13 | err := Wrapf(io.EOF, "user %d: cannot read file %q", 42, "/etc/hosts") 14 | require.Error(t, err) 15 | assert.EqualError(t, err, `user 42: cannot read file "/etc/hosts": EOF`) 16 | assert.ErrorIs(t, err, io.EOF) 17 | }) 18 | 19 | t.Run("nil wrapping", func(t *testing.T) { 20 | err := Wrapf(nil, "some message") 21 | assert.Nil(t, err) 22 | }) 23 | 24 | t.Run("nil wrapping with args", func(t *testing.T) { 25 | err := Wrapf(nil, "user %d", 42) 26 | assert.Nil(t, err) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-with-secondary-error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/cockroachdb/errors" 9 | ) 10 | 11 | func main() { 12 | err := io.EOF 13 | err = errors.WithSecondaryError(err, context.Canceled) 14 | 15 | fmt.Println(err) 16 | /* 17 | EOF 18 | */ 19 | 20 | fmt.Printf("%+v", err) 21 | /* 22 | EOF 23 | (1) secondary error attachment 24 | | context canceled 25 | | (1) context canceled 26 | | Error types: (1) *errors.errorString 27 | Wraps: (2) EOF 28 | Error types: (1) *secondary.withSecondaryError (2) *errors.errorString 29 | */ 30 | 31 | fmt.Println() 32 | fmt.Println(errors.Is(err, io.EOF)) // true 33 | fmt.Println(errors.Is(err, context.Canceled)) // false 34 | } 35 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-basic-2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | var ( 11 | errNotFound = errors.New("not found") 12 | errUnauthorized = errors.New("unauthorized") 13 | errUnknown = errors.New("unknown error") 14 | ) 15 | 16 | func main() { 17 | var eg errgroup.Group 18 | 19 | eg.Go(func() error { return errNotFound }) 20 | eg.Go(func() error { return errUnauthorized }) 21 | eg.Go(func() error { return errUnknown }) 22 | 23 | switch err := eg.Wait(); { 24 | case errors.Is(err, errNotFound): 25 | fmt.Println("1") 26 | 27 | case errors.Is(err, errUnauthorized): 28 | fmt.Println("2") 29 | 30 | case errors.Is(err, errUnknown): 31 | fmt.Println("3") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/error-with-stacktrace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime/debug" 6 | ) 7 | 8 | type WithStacktraceError struct { 9 | message string 10 | stacktrace []byte 11 | } 12 | 13 | func (w *WithStacktraceError) Error() string { 14 | return w.message 15 | } 16 | 17 | func (w *WithStacktraceError) StackTrace() string { 18 | return string(w.stacktrace) 19 | } 20 | 21 | func doSomething() error { 22 | return &WithStacktraceError{ 23 | message: "something went wrong", 24 | stacktrace: debug.Stack(), 25 | } 26 | } 27 | 28 | func main() { 29 | if err := doSomething(); err != nil { 30 | if stacktraceErr, ok := err.(*WithStacktraceError); ok { 31 | fmt.Printf("%s\n%s", stacktraceErr.Error(), stacktraceErr.StackTrace()) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/hashicorp-multierror-replacement/map.go: -------------------------------------------------------------------------------- 1 | package multierror 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/go-multierror" 7 | ) 8 | 9 | // MapV2 работает как Map, только реализована на базе стандартной библиотеки. 10 | func MapV2[T any](fn func(v T) error, input []T) error { 11 | // Реализуй меня. 12 | return nil 13 | } 14 | 15 | // Map применяет функцию fn к каждому элементу input. 16 | // Если вызов fn завершается ошибкой, то ошибка добавляется в результирующую ошибку, возвращаемую Map. 17 | func Map[T any](fn func(v T) error, input []T) error { 18 | var result error 19 | for i, s := range input { 20 | if err := fn(s); err != nil { 21 | result = multierror.Append(result, fmt.Errorf("elem at %d: %w", i, err)) 22 | } 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-simple-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | func main() { 11 | if err := work(); err != nil { 12 | fmt.Println(err) // something bad has happened 13 | } 14 | } 15 | 16 | func work() error { 17 | var eg errgroup.Group 18 | 19 | eg.Go(func() error { 20 | // Выполняем какую-то операцию, завершившуюся с ошибкой. 21 | // ... 22 | return errors.New("something bad has happened") 23 | }) 24 | 25 | eg.Go(func() error { 26 | // Выполняем какую-то операцию, завершившуюся без ошибки. 27 | // ... 28 | return nil 29 | }) 30 | 31 | // Дожидаемся окончания работ. 32 | // Возвращаем ошибку от любой из операций (если ошибка произошла). 33 | return eg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-error-types/handler.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | const defaultPostpone = time.Second 9 | 10 | type Job struct { 11 | ID int 12 | } 13 | 14 | type Handler struct{} 15 | 16 | func (h *Handler) Handle(job Job) (postpone time.Duration, err error) { 17 | err = h.process(job) 18 | if err != nil { 19 | // Обработайте ошибку. 20 | } 21 | 22 | return 0, nil 23 | } 24 | 25 | func (h *Handler) process(job Job) error { 26 | switch job.ID { 27 | case 1: 28 | return &InconsistentDataError{} 29 | case 2: 30 | return &NotReadyError{} 31 | case 3: 32 | return &NotFoundError{} 33 | case 4: 34 | return &AlreadyDoneError{} 35 | case 5: 36 | return &InvalidIDError{} 37 | case 6: 38 | return io.EOF 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/cause-and-is/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | var errTarget = stderrors.New("target error") 11 | 12 | func checkError(err error) { 13 | switch { 14 | case errors.Cause(err) == errTarget: 15 | fmt.Println("errors.Cause") 16 | 17 | case errors.Is(err, errTarget): 18 | fmt.Println("errors.Is") 19 | 20 | default: 21 | fmt.Println("default") 22 | } 23 | } 24 | 25 | func main() { 26 | { 27 | err := stderrors.New("target error") 28 | err = fmt.Errorf("oops: %w", err) 29 | checkError(err) 30 | } 31 | 32 | { 33 | err := fmt.Errorf("oops: %w", errTarget) 34 | checkError(err) 35 | } 36 | 37 | { 38 | err := errors.Wrap(errTarget, "oops") 39 | checkError(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-vs-fmt-3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | var ErrNotFound = stderrors.New("not found") 12 | 13 | func main() { 14 | { 15 | err := fmt.Errorf("index.html: %w", ErrNotFound) 16 | fmt.Println(err, "|", errors.Is(err, ErrNotFound)) // index.html: not found | true 17 | } 18 | 19 | { 20 | err := errors.Errorf("index.html: %w", ErrNotFound) 21 | fmt.Println(err, "|", errors.Is(err, ErrNotFound)) // index.html: %!w(*errors.errorString=&{not found}) | false 22 | } 23 | 24 | { 25 | err := errors.Wrapf(ErrNotFound, "index.html: %w", io.EOF) 26 | fmt.Println(err, "|", errors.Is(err, ErrNotFound)) // index.html: %!w(*errors.errorString=&{not found}) | true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/resp-body-close-func/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | index, err := httpGet("http://www.golang-ninja.ru/") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | fmt.Println(string(index)[:300]) 17 | } 18 | 19 | func httpGet(url string) ([]byte, error) { 20 | res, err := http.Get(url) //nolint:noctx 21 | if err != nil { 22 | return nil, fmt.Errorf("cannot do GET: %v", err) 23 | } 24 | defer res.Body.Close() 25 | 26 | body, err := io.ReadAll(res.Body) 27 | if err != nil { 28 | return nil, fmt.Errorf("cannot read body: %v", err) 29 | } 30 | 31 | if err := res.Body.Close(); err != nil { 32 | return nil, fmt.Errorf("cannot close body: %v", err) 33 | } 34 | return body, nil 35 | } 36 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "allocator.h" 5 | 6 | int main() 7 | { 8 | size_t size = 0; 9 | int uid = 0; 10 | 11 | errno = 0; 12 | if (scanf("%d %zu", &uid, &size) != 2) { 13 | perror("scanf failed"); 14 | exit(1); 15 | } 16 | 17 | void *p = NULL; 18 | errno_t err = allocate(uid, size, &p); 19 | if (err != 0) { 20 | printf("allocation error: %s\n", strerror(err)); 21 | exit(0); // Считаем валидной ситуацией. 22 | } 23 | if (p == NULL) { 24 | printf("memory pointer is NULL after allocation"); 25 | exit(1); 26 | } 27 | 28 | printf("allocation was successful for %zu bytes\n", size); 29 | free(p); 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/write-to-file/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | var accessLogFilename = filepath.Join(os.TempDir(), "access.log") 11 | 12 | func main() { 13 | fmt.Println(accessLogFilename) 14 | 15 | if err := writeToAccessLog("GET /"); err != nil { 16 | log.Fatal(err) 17 | } 18 | } 19 | 20 | func writeToAccessLog(data string) error { 21 | f, err := os.OpenFile(accessLogFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) 22 | if err != nil { 23 | return fmt.Errorf("cannot open file: %v", err) 24 | } 25 | defer f.Close() 26 | 27 | if _, err := f.WriteString(data + "\n"); err != nil { 28 | return fmt.Errorf("cannot write data: %v", err) 29 | } 30 | 31 | // return f.Close() 32 | // return nil 33 | return f.Sync() 34 | } 35 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/cats-unmarshalling-gotcha/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type Cat struct { 10 | Name string `json:"name"` 11 | } 12 | 13 | func main() { 14 | catsJSONs := []string{`{"name": "Bobby"}`, `"name": "Billy"`, `{"name": "Васёк"}`} 15 | catsCh := make(chan Cat, len(catsJSONs)) 16 | 17 | var wg sync.WaitGroup 18 | wg.Add(len(catsJSONs)) 19 | 20 | var err error // Заводим специальную переменную для хранения ошибки. 21 | 22 | for _, catData := range catsJSONs { 23 | go func(catData string) { 24 | defer wg.Done() 25 | 26 | var cat Cat 27 | err = json.Unmarshal([]byte(catData), &cat) 28 | catsCh <- cat 29 | }(catData) 30 | } 31 | 32 | wg.Wait() 33 | fmt.Println(err) // Выводим ошибку на экран. 34 | } 35 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/combine-errors/combine_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | /* 10 | Для авторского решения: 11 | 12 | $ go test -benchmem -bench . 13 | BenchmarkCombine-8 2538996 643.3 ns/op 200 B/op 6 allocs/op 14 | PASS 15 | ok 04-non-standard-modules/combine-errors 2.556s 16 | */ 17 | 18 | func BenchmarkCombine(b *testing.B) { 19 | var ( 20 | err = errors.New("an error") 21 | err2 = errors.New("an error 2") 22 | err3 = errors.New("an error 3") 23 | err4 = errors.New("an error 4") 24 | ) 25 | 26 | b.ResetTimer() 27 | for i := 0; i < b.N; i++ { 28 | combinedErr := Combine(err, err2, err3, err4) 29 | _ = fmt.Sprint(combinedErr) 30 | _ = fmt.Sprintf("%+v", combinedErr) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/error-with-stacktrace-opaque/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime/debug" 6 | ) 7 | 8 | type WithStacktraceError struct { 9 | message string 10 | stacktrace []byte 11 | } 12 | 13 | func (w *WithStacktraceError) Error() string { 14 | return w.message 15 | } 16 | 17 | func (w *WithStacktraceError) StackTrace() string { 18 | return string(w.stacktrace) 19 | } 20 | 21 | func doSomething() error { 22 | return &WithStacktraceError{ 23 | message: "something went wrong", 24 | stacktrace: debug.Stack(), 25 | } 26 | } 27 | 28 | func main() { 29 | if err := doSomething(); err != nil { 30 | type stackTracer interface { 31 | StackTrace() string 32 | } 33 | if st, ok := err.(stackTracer); ok { 34 | fmt.Printf("%s\n%s", err, st.StackTrace()) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/middleware/client.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cockroachdb/errors" 7 | "github.com/gogo/status" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | func UnaryClientInterceptor( 12 | ctx context.Context, 13 | method string, 14 | req interface{}, 15 | reply interface{}, 16 | cc *grpc.ClientConn, 17 | invoker grpc.UnaryInvoker, 18 | opts ...grpc.CallOption, 19 | ) error { 20 | err := invoker(ctx, method, req, reply, cc, opts...) 21 | 22 | st := status.Convert(err) 23 | var reconstituted error 24 | for _, det := range st.Details() { 25 | if t, ok := det.(*errors.EncodedError); ok { 26 | reconstituted = errors.DecodeError(ctx, *t) 27 | } 28 | } 29 | 30 | if reconstituted != nil { 31 | err = reconstituted 32 | } 33 | 34 | return err 35 | } 36 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/uber-multierr-append-invoke-named-gotcha/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "go.uber.org/multierr" 8 | ) 9 | 10 | func main() { 11 | err := good() 12 | fmt.Printf("good: %v\n", err) // good: unexpected EOF 13 | 14 | err2 := bad() 15 | fmt.Printf("bad: %v\n", err2) // bad: , хотя должен вывести "unexpected EOF" 16 | } 17 | 18 | var errInvoker = multierr.Invoke(func() error { return io.ErrUnexpectedEOF }) 19 | 20 | func good() (err error) { 21 | err = getError() 22 | defer multierr.AppendInvoke(&err, errInvoker) 23 | return nil 24 | } 25 | 26 | // https://golang.org/ref/spec#Return_statements 27 | func bad() error { 28 | err := getError() 29 | defer multierr.AppendInvoke(&err, errInvoker) 30 | return err 31 | } 32 | 33 | func getError() error { 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/gotcha-err-iface-1/handler_test.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func ExampleHandler() { 12 | if err := Handle(); err != nil { 13 | fmt.Println("handle err:", err) 14 | } else { 15 | fmt.Println("no handle err") 16 | } 17 | 18 | // Output: 19 | // no handle err 20 | } 21 | 22 | func TestHandle(t *testing.T) { 23 | t.Run("no error", func(t *testing.T) { 24 | assert.NoError(t, Handle()) 25 | }) 26 | 27 | t.Run("internal server error", func(t *testing.T) { 28 | defer func() { 29 | usefulWork = func() error { return nil } 30 | }() 31 | 32 | usefulWork = func() error { 33 | return errors.New("something wrong") 34 | } 35 | assert.ErrorIs(t, Handle(), ErrInternalServerError) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/pkg-errors-wrap-nil-gotcha/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func main() { 10 | listener, err := createEventListener() 11 | if err != nil { 12 | fmt.Println(err) 13 | return 14 | } 15 | 16 | if err := listener(); err != nil { 17 | fmt.Println(err) 18 | } 19 | } 20 | 21 | func createEventListener() (func() error, error) { 22 | obj, err := getObject() 23 | if err != nil { 24 | return nil, errors.Wrap(err, "get object") 25 | } 26 | 27 | if obj.ID == 42 { 28 | return nil, errors.Wrap(err, "events is not supported for this obj") 29 | } 30 | 31 | return func() error { 32 | fmt.Println("ok") 33 | return nil 34 | }, nil 35 | } 36 | 37 | type Object struct { 38 | ID int 39 | } 40 | 41 | func getObject() (*Object, error) { 42 | return &Object{ID: 42}, nil 43 | } 44 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-with-context/network_request.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | func networkRequest(ctx context.Context, url string) ([]byte, error) { 11 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 12 | if err != nil { 13 | return nil, fmt.Errorf("build request: %v", err) 14 | } 15 | 16 | resp, err := http.DefaultClient.Do(req) 17 | if err != nil { 18 | return nil, fmt.Errorf("do request: %v", err) 19 | } 20 | defer resp.Body.Close() 21 | 22 | body, err := io.ReadAll(resp.Body) 23 | if err != nil { 24 | return nil, fmt.Errorf("read body: %v", err) 25 | } 26 | 27 | if err = resp.Body.Close(); err != nil { 28 | return nil, fmt.Errorf("close body: %v", err) 29 | } 30 | 31 | fmt.Printf("requesting %q - OK\n", url) 32 | return body, nil 33 | } 34 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/cats-unmarshalling/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type Cat struct { 10 | Name string `json:"name"` 11 | } 12 | 13 | func main() { 14 | catsJSONs := []string{`{"name": "Bobby"}`, `"name": "Billy"`, `{"name": "Васёк"}`} 15 | catsCh := make(chan Cat) 16 | 17 | var wg sync.WaitGroup 18 | wg.Add(len(catsJSONs)) 19 | 20 | for _, catData := range catsJSONs { 21 | go func(catData string) { // Разбираем котиков в несколько горутин. 22 | defer wg.Done() 23 | 24 | var cat Cat 25 | if err := json.Unmarshal([]byte(catData), &cat); err != nil { 26 | // Случилась ошибка, что делать? 27 | } 28 | catsCh <- cat 29 | }(catData) 30 | } 31 | 32 | go func() { 33 | wg.Wait() 34 | close(catsCh) 35 | }() 36 | 37 | for cat := range catsCh { 38 | fmt.Println(cat) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/docker-err/docker.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import "context" 4 | 5 | type Executor interface { 6 | Exec(ctx context.Context, cmd string, args ...any) error 7 | } 8 | 9 | type Docker struct{} 10 | 11 | func (d *Docker) RunContainer(ctx context.Context, e Executor, image string) error { 12 | if err := e.Exec(ctx, "run", image); err != nil { 13 | return newDockerError(err) 14 | } 15 | return nil 16 | } 17 | 18 | func (d *Docker) StopContainer(ctx context.Context, e Executor, containerID string) error { 19 | if err := e.Exec(ctx, "stop", containerID); err != nil { 20 | return newDockerError(err) 21 | } 22 | return nil 23 | } 24 | 25 | func (d *Docker) ExecContainerCmd(ctx context.Context, e Executor, containerID, cmd string) error { 26 | if err := e.Exec(ctx, "exec", containerID, cmd); err != nil { 27 | return newDockerError(err) 28 | } 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/byte-buffer-initial/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type ByteBuffer struct { 6 | // buffer представляет собой непосредственно буфер: содержит какие-то данные. 7 | buffer []byte 8 | // offset представляет собой смещение, указывающее на первый непрочитанный байт. 9 | offset int 10 | } 11 | 12 | func (b *ByteBuffer) Write(p []byte) int { 13 | b.buffer = append(b.buffer, p...) 14 | return len(p) 15 | } 16 | 17 | func (b *ByteBuffer) Read(p []byte) int { 18 | if b.offset >= len(b.buffer) { 19 | return 0 20 | } 21 | 22 | n := copy(p, b.buffer[b.offset:]) 23 | b.offset += n 24 | return n 25 | } 26 | 27 | func main() { 28 | var b ByteBuffer 29 | b.Write([]byte("hello hello hello")) 30 | 31 | p := make([]byte, 3) 32 | for { 33 | n := b.Read(p) 34 | if n == 0 { 35 | break 36 | } 37 | fmt.Print(string(p[:n])) // hello hello hello 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-expensive-stack-wrap-with-msg/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | // go test -tags std -benchmem -bench . > std.txt 9 | // go test -tags pkg.msg.stack -benchmem -bench . > pkg-msg-stack.txt 10 | // go test -tags pkg.msg.only -benchmem -bench . > pkg-msg-only.txt 11 | // benchstat -alpha 1.1 std.txt pkg-msg-only.txt 12 | // benchstat -alpha 1.1 std.txt pkg-msg-stack.txt 13 | 14 | // ErrGlobal экспортируемая переменная уровня пакета, 15 | // необходимая для предотвращений оптимизаций компилятора. 16 | var ErrGlobal error 17 | 18 | var depths = []int{1, 2, 4, 8, 16, 32} 19 | 20 | func BenchmarkGimmeDeepError(b *testing.B) { 21 | for _, depth := range depths { 22 | b.Run(strconv.Itoa(depth), func(b *testing.B) { 23 | for i := 0; i < b.N; i++ { 24 | ErrGlobal = GimmeDeepError(depth) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/naming/main.go: -------------------------------------------------------------------------------- 1 | //nolint:deadcode,unused,varcheck 2 | package naming 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | // Sentinel errors. 11 | // Название начинается с приставки err или Err. 12 | errNotFound = errors.New("error not found") // Неэкспортируемая. 13 | ErrNotFound = errors.New("error not found") // Экспортируемая. 14 | ) 15 | 16 | // Кастомный тип ошибки. Название заканчивается на Error. 17 | type NotFoundError struct { 18 | page string 19 | } 20 | 21 | func (e *NotFoundError) Error() string { 22 | return fmt.Sprintf("page %q not found", e.page) 23 | } 24 | 25 | var ( 26 | // Sentinel errors. 27 | // Точно так же название начинается с приставки err или Err. 28 | errNotFound2 = &NotFoundError{"https://www.golang-ninja.ru/"} // Неэкспортируемая. 29 | ErrNotFound2 = &NotFoundError{"https://www.golang-ninja.ru/"} // Экспортируемая. 30 | ) 31 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-panic-and-recover/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "emperror.dev/emperror" 7 | "emperror.dev/errors" 8 | ) 9 | 10 | var panicHandler = emperror.ErrorHandlerFunc(func(err error) { 11 | if err == nil { 12 | return 13 | } 14 | // Логируем ошибку, например. 15 | fmt.Println("panic handler called:", err) 16 | }) 17 | 18 | func main() { 19 | // Recover паники и обработка её как ошибки. 20 | defer emperror.HandleRecover(panicHandler) 21 | 22 | // nil-ошибка панику не вызовет. 23 | emperror.Panic(nil) 24 | 25 | // Будет паника, если doSomething вернёт ошибку. 26 | // emperror.Panic полезно использовать при инициализации компонентов приложения на его старте, 27 | // когда при возникновении ошибки надо падать. 28 | emperror.Panic(doSomething()) 29 | } 30 | 31 | func doSomething() error { 32 | return errors.New("error from doSomething") 33 | } 34 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/monad/monad.go: -------------------------------------------------------------------------------- 1 | package monad 2 | 3 | import "errors" 4 | 5 | var ErrNoMonadValue = errors.New("no monad value") 6 | 7 | // M представляет собой монаду. 8 | type M struct { 9 | err error 10 | v any 11 | } 12 | 13 | // Bind применяет функцию f к значению M, возвращая новую монаду. 14 | // Если M невалидна, то Bind эффекта не имеет. 15 | func (m M) Bind(f func(v any) M) M { 16 | // Реализуй меня. 17 | return M{} 18 | } 19 | 20 | // Unpack возвращает значение и ошибку, хранимые в монаде. 21 | // При отсутствии и ошибки и значения метод возвращает ErrNoMonadValue. 22 | func (m M) Unpack() (any, error) { 23 | // Реализуй меня. 24 | return nil, nil 25 | } 26 | 27 | // Unit конструирует M на основе значения v. 28 | func Unit(v any) M { 29 | // Реализуй меня. 30 | return M{} 31 | } 32 | 33 | // Err конструирует "невалидную" монаду M. 34 | func Err(err error) M { 35 | // Реализуй меня. 36 | return M{} 37 | } 38 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/get-index-from-filename/index.go: -------------------------------------------------------------------------------- 1 | package index 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const prefix = "parsed_page_" // parsed_page_100 11 | 12 | var ( 13 | ErrInvalidFilename = errors.New("invalid filename") 14 | ErrIndexMustBePositive = errors.New("index must be > 0") 15 | ) 16 | 17 | func GetIndexFromFileName(fileName string) (int, error) { 18 | parts := strings.Split(fileName, prefix) 19 | if len(parts) != 2 || parts[1] == "" { 20 | return 0, fmt.Errorf("%w: no index in filename %q", ErrInvalidFilename, fileName) 21 | } 22 | 23 | num := parts[1] 24 | 25 | index, err := strconv.ParseInt(num, 10, 32) 26 | if err != nil { 27 | return 0, fmt.Errorf("cannot parse index as int: %w", err) 28 | } 29 | 30 | if index <= 0 { 31 | return 0, fmt.Errorf("%w: got %d", ErrIndexMustBePositive, index) 32 | } 33 | 34 | return int(index), nil 35 | } 36 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-filtering/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "emperror.dev/emperror" 8 | ) 9 | 10 | var ( 11 | err1 = errors.New("error 1") 12 | err2 = errors.New("error 2") 13 | errsToSkip = []error{err1, err2} 14 | 15 | errHandler = emperror.ErrorHandlerFunc(func(err error) { 16 | if err == nil { 17 | return 18 | } 19 | fmt.Printf("error handler called: %v\n", err) 20 | }) 21 | 22 | errMatcher = emperror.ErrorMatcher(func(err error) bool { 23 | for i := range errsToSkip { 24 | if needDiscard := errors.Is(err, errsToSkip[i]); needDiscard { 25 | return true 26 | } 27 | } 28 | return false 29 | }) 30 | ) 31 | 32 | func main() { 33 | handler := emperror.WithFilter(errHandler, errMatcher) 34 | 35 | handler.Handle(err1) // Обработчик не сработает. 36 | handler.Handle(errors.New("unknown error")) // Обработчик сработает. 37 | } 38 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-with-context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "golang.org/x/sync/errgroup" 10 | ) 11 | 12 | func main() { 13 | ctx, cancel := context.WithCancel(context.Background()) 14 | go func() { 15 | select { 16 | case <-ctx.Done(): 17 | fmt.Print("5") 18 | case <-time.After(3 * time.Second): 19 | cancel() 20 | } 21 | }() 22 | 23 | eg, egCtx := errgroup.WithContext(ctx) 24 | 25 | eg.Go(func() error { 26 | select { 27 | case <-egCtx.Done(): 28 | fmt.Print("1") 29 | 30 | case <-time.After(10 * time.Millisecond): 31 | return errors.New("2") 32 | } 33 | 34 | return nil 35 | }) 36 | 37 | eg.Go(func() error { 38 | select { 39 | case <-egCtx.Done(): 40 | fmt.Print("3") 41 | 42 | case <-time.After(time.Second): 43 | fmt.Print("4") 44 | } 45 | 46 | return nil 47 | }) 48 | 49 | fmt.Print(eg.Wait()) 50 | } 51 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/mini-word/document_bench_test.go: -------------------------------------------------------------------------------- 1 | package miniword 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | /* 9 | Для авторского решения: 10 | 11 | $ go test -benchmem -bench . 12 | BenchmarkDoc-8 163558 6652 ns/op 72608 B/op 18 allocs/op 13 | PASS 14 | ok 05-errors-best-practices/mini-word 1.251s 15 | */ 16 | 17 | func BenchmarkDoc(b *testing.B) { 18 | var w dummyWriter 19 | text := strings.Repeat("A", 100) 20 | 21 | b.ResetTimer() 22 | for i := 0; i < b.N; i++ { 23 | d := NewDocument() 24 | 25 | for i := 2; i <= 3; i++ { 26 | d.AddPage() 27 | d.SetActivePage(i) 28 | for i := 0; i < 100; i++ { 29 | d.WriteText(text) 30 | } 31 | } 32 | 33 | if _, err := d.WriteTo(w); err != nil { 34 | b.Fatal(err) 35 | } 36 | } 37 | } 38 | 39 | type dummyWriter struct{} 40 | 41 | func (d dummyWriter) Write(_ []byte) (n int, err error) { 42 | return 0, nil 43 | } 44 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-mark-gotcha/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | stderrors "errors" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/cockroachdb/errors" 9 | ) 10 | 11 | type NotFoundError struct { 12 | message string 13 | } 14 | 15 | func (e *NotFoundError) Error() string { 16 | return e.message 17 | } 18 | 19 | func main() { 20 | err1 := &NotFoundError{"object not found"} 21 | err2 := &NotFoundError{"object not found"} 22 | 23 | err := io.ErrUnexpectedEOF 24 | err = errors.Mark(err, err1) 25 | err = errors.Wrap(err, "something other happened") 26 | 27 | if errors.Is(err, err1) { // Ожидаемо true. 28 | fmt.Println("err is err1") 29 | } 30 | 31 | if errors.Is(err, err2) { // Не очень ожидаемо true. 32 | fmt.Println("err is err2") 33 | } 34 | 35 | if errors.Is(err1, err2) { // Ещё менее ожидаемо true. 36 | fmt.Println("err1 is err2") 37 | } 38 | 39 | if stderrors.Is(err1, err2) { // Ожидаемо false. 40 | fmt.Println("err1 is err2") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-opaque-errors/errors.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | type AlreadyDoneError struct{} 4 | 5 | func (e *AlreadyDoneError) Error() string { return "job is already done" } 6 | func (e *AlreadyDoneError) Skip() bool { return true } 7 | 8 | type InconsistentDataError struct{} 9 | 10 | func (e *InconsistentDataError) Error() string { return "job payload is corrupted" } 11 | func (e *InconsistentDataError) Skip() bool { return true } 12 | 13 | type InvalidIDError struct{} 14 | 15 | func (e *InvalidIDError) Error() string { return "invalid job id" } 16 | func (e *InvalidIDError) Skip() bool { return true } 17 | 18 | type NotFoundError struct{} 19 | 20 | func (e *NotFoundError) Error() string { return "job wasn't found" } 21 | func (e *NotFoundError) Skip() bool { return true } 22 | 23 | type NotReadyError struct{} 24 | 25 | func (e *NotReadyError) Error() string { return "job is not ready to be performed" } 26 | func (e *NotReadyError) Temporary() bool { return true } 27 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-opaque-errors/handler.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | const defaultPostpone = time.Second 9 | 10 | type Job struct { 11 | ID int 12 | } 13 | 14 | type Handler struct{} 15 | 16 | func (h *Handler) Handle(job Job) (postpone time.Duration, err error) { 17 | err = h.process(job) 18 | if err != nil { 19 | // Обработайте ошибку. 20 | } 21 | 22 | return 0, nil 23 | } 24 | 25 | func isTemporary(err error) bool { 26 | // Реализуй меня. 27 | return false 28 | } 29 | 30 | func shouldBeSkipped(err error) bool { 31 | // Реализуй меня. 32 | return false 33 | } 34 | 35 | func (h *Handler) process(job Job) error { 36 | switch job.ID { 37 | case 1: 38 | return &InconsistentDataError{} 39 | case 2: 40 | return &NotReadyError{} 41 | case 3: 42 | return &NotFoundError{} 43 | case 4: 44 | return &AlreadyDoneError{} 45 | case 5: 46 | return &InvalidIDError{} 47 | case 6: 48 | return io.EOF 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/waitgroup-simple-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | func main() { 10 | if err := work(); err != nil { 11 | fmt.Println(err) // something bad has happened 12 | } 13 | } 14 | 15 | func work() error { 16 | // Будем выполнять два параллельных действия. 17 | var wg sync.WaitGroup 18 | errsCh := make(chan error, 2) 19 | 20 | wg.Add(1) 21 | go func() { 22 | defer wg.Done() 23 | // Выполняем какую-то операцию, завершившуюся с ошибкой. 24 | // ... 25 | errsCh <- errors.New("something bad has happened") 26 | }() 27 | 28 | wg.Add(1) 29 | go func() { 30 | defer wg.Done() 31 | // Выполняем какую-то операцию, завершившуюся без ошибки. 32 | // ... 33 | }() 34 | 35 | wg.Wait() // Дожидаемся окончания работ. 36 | 37 | // Возвращаем ошибку от любой из операций (если ошибка произошла). 38 | select { 39 | case err := <-errsCh: 40 | return err 41 | default: 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-panic-and-recover/client-server/server/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func Handle(w http.ResponseWriter, r *http.Request) { 10 | body, err := ioutil.ReadAll(r.Body) 11 | if err != nil { 12 | internalServerError(w, "error while reading request body") 13 | return 14 | } 15 | 16 | switch string(body) { 17 | case "panic": 18 | panic("client wants me to panic") 19 | 20 | case "error": 21 | ok(w, "internal server error") 22 | 23 | default: 24 | ok(w, "ok") 25 | } 26 | } 27 | 28 | func internalServerError(w http.ResponseWriter, errMsg string) { 29 | w.WriteHeader(http.StatusInternalServerError) 30 | safeWrite(w, errMsg) 31 | } 32 | 33 | func ok(w http.ResponseWriter, msg string) { 34 | w.WriteHeader(http.StatusOK) 35 | safeWrite(w, msg) 36 | } 37 | 38 | func safeWrite(w http.ResponseWriter, msg string) { 39 | if _, err := w.Write([]byte(msg)); err != nil { 40 | log.Println(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap-in-diff-ways/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func main() { 11 | wrappedErr := os.ErrNotExist 12 | 13 | cases := []struct { 14 | title string 15 | err error 16 | }{ 17 | { 18 | title: "only std errors", 19 | err: fmt.Errorf("msg 2: %w", fmt.Errorf("msg 1: %w", wrappedErr)), 20 | }, 21 | { 22 | title: "only pkg/errors errors", 23 | err: errors.Wrap(errors.Wrap(wrappedErr, "msg 1"), " msg 2"), 24 | }, 25 | { 26 | title: "combined 1", 27 | err: errors.Wrap(fmt.Errorf("msg 1: %w", wrappedErr), "msg 2"), 28 | }, 29 | { 30 | title: "combined 2", 31 | err: fmt.Errorf("msg 2: %w", errors.Wrap(wrappedErr, "msg 1")), 32 | }, 33 | } 34 | 35 | for _, c := range cases { 36 | fmt.Println(c.title) 37 | fmt.Println("\terrors.Is:", errors.Is(c.err, wrappedErr)) 38 | fmt.Println("\terrors.Cause:", errors.Cause(c.err) == wrappedErr) 39 | fmt.Println() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/server_test.go: -------------------------------------------------------------------------------- 1 | package cockroachgrpc 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/cockroachdb/errors" 7 | "github.com/cockroachdb/errors/grpc/status" 8 | "google.golang.org/grpc/codes" 9 | ) 10 | 11 | var ( 12 | ErrCantEcho = errors.New("unable to echo") 13 | ErrTooLong = errors.New("text is too long") 14 | ErrInternal = errors.New("internal error") 15 | ) 16 | 17 | type EchoServer struct{} 18 | 19 | func (srv *EchoServer) Echo(ctx context.Context, req *EchoRequest) (*EchoReply, error) { 20 | msg := req.Text 21 | switch { 22 | case msg == "noecho": 23 | return nil, ErrCantEcho 24 | case len(msg) > 10: 25 | return nil, errors.WithMessage(ErrTooLong, msg+" is too long") 26 | case msg == "reverse": 27 | return nil, status.Error(codes.Unimplemented, "reverse is not implemented") 28 | case msg == "internal": 29 | return nil, status.WrapErr(codes.Internal, "there was a problem", ErrInternal) 30 | } 31 | return &EchoReply{ 32 | Reply: "echoing: " + msg, 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/not-found-error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type HTTPError interface { 9 | error 10 | StatusCode() int 11 | } 12 | 13 | type NotFoundError struct{} 14 | 15 | func (n *NotFoundError) Error() string { 16 | return "Not Found" 17 | } 18 | 19 | func (n NotFoundError) StatusCode() int { 20 | return http.StatusNotFound 21 | } 22 | 23 | /* 24 | Тип NotFoundError имеет множество методов T: 25 | - StatusCode 26 | 27 | Тип *NotFoundError имеет множество методов T + *T: 28 | - Error 29 | - StatusCode 30 | 31 | Как следствие именно *NotFoundError реализует интерфейс HTTPError. 32 | */ 33 | func main() { 34 | var err HTTPError = &NotFoundError{} 35 | fmt.Println(err.StatusCode(), err.Error()) // 404 Not Found 36 | 37 | // Не скомпилируется: 38 | // var _ HTTPError = NotFoundError{} 39 | // cannot use NotFoundError{} (type NotFoundError) as type HTTPError in assignment: 40 | // NotFoundError does not implement HTTPError (Error method has pointer receiver) 41 | } 42 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/status-error/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | ErrNotFound = NewStatusError(http.StatusNotFound, "Not Found") 12 | ErrNotFound2 = NewStatusError(http.StatusNotFound, "Not Found") 13 | ) 14 | 15 | type StatusError struct { 16 | code int 17 | msg string 18 | } 19 | 20 | func (s *StatusError) Error() string { 21 | return fmt.Sprintf("%d %s", s.code, s.msg) 22 | } 23 | 24 | func NewStatusError(code int, msg string) *StatusError { 25 | return &StatusError{code: code, msg: msg} 26 | } 27 | 28 | func main() { 29 | fmt.Println(ErrNotFound) // 404 Not Found 30 | fmt.Println(unsafe.Sizeof(ErrNotFound)) // 8 (pointer). 31 | 32 | fmt.Println(ErrNotFound == ErrNotFound2) // false 33 | fmt.Println(errors.Is(ErrNotFound, ErrNotFound2)) // false 34 | 35 | var se1 error = &StatusError{code: http.StatusNotFound} 36 | var se2 error = &StatusError{code: http.StatusNotFound} 37 | fmt.Println(se1 == se2) // false 38 | } 39 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/hashicorp-multierror/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | "github.com/hashicorp/go-multierror" 10 | ) 11 | 12 | func main() { 13 | err1 := errors.New("an error 1") 14 | err2 := errors.New("an error 2") 15 | 16 | err := multierror.Append(io.EOF, err1, err2) 17 | 18 | fmt.Println(errors.Is(err, io.EOF)) // true 19 | fmt.Println(errors.Is(err, err1)) // true 20 | fmt.Println(errors.Is(err, err2)) // true 21 | fmt.Println() 22 | 23 | fmt.Println(err) 24 | /* 25 | 3 errors occurred: 26 | * EOF 27 | * an error 1 28 | * an error 2 29 | */ 30 | 31 | err.ErrorFormat = func(errors []error) string { 32 | var b strings.Builder 33 | b.WriteString("MY ERRORS:\n") 34 | for _, err := range errors { 35 | b.WriteString("\t - " + err.Error() + "\n") 36 | } 37 | return b.String() 38 | } 39 | fmt.Println(err) 40 | /* 41 | MY ERRORS: 42 | - EOF 43 | - an error 1 44 | - an error 2 45 | */ 46 | } 47 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-panic-and-recover/client-server/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | const url = "http://127.0.0.1:8888/" 12 | 13 | var ( 14 | regularRequest = []byte("regular") 15 | errorRequest = []byte("error") 16 | panicRequest = []byte("panic") 17 | ) 18 | 19 | func main() { 20 | for _, request := range [][]byte{regularRequest, errorRequest, panicRequest} { 21 | if err := makeRequest(request); err != nil { 22 | log.Println(err) 23 | } 24 | } 25 | } 26 | 27 | func makeRequest(request []byte) error { 28 | resp, err := http.Post(url, "application/json", bytes.NewBuffer(request)) //nolint:noctx 29 | if err != nil { 30 | return fmt.Errorf("do post: %v", err) 31 | } 32 | defer resp.Body.Close() 33 | 34 | body, err := ioutil.ReadAll(resp.Body) 35 | if err != nil { 36 | return fmt.Errorf("read body: %v", err) 37 | } 38 | 39 | log.Printf("HTTP code: %d, Body: %s", resp.StatusCode, string(body)) 40 | return resp.Body.Close() 41 | } 42 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-sentinel-errors/handler.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | const defaultPostpone = time.Second 9 | 10 | var ( 11 | ErrAlreadyDone error = new(AlreadyDoneError) 12 | ErrInconsistentData error = new(InconsistentDataError) 13 | ErrInvalidID error = new(InvalidIDError) 14 | ErrNotFound error = new(NotFoundError) 15 | ErrNotReady error = new(NotReadyError) 16 | ) 17 | 18 | type Job struct { 19 | ID int 20 | } 21 | 22 | type Handler struct{} 23 | 24 | func (h *Handler) Handle(job Job) (postpone time.Duration, err error) { 25 | err = h.process(job) 26 | if err != nil { 27 | // Обработайте ошибку. 28 | } 29 | 30 | return 0, nil 31 | } 32 | 33 | func (h *Handler) process(job Job) error { 34 | switch job.ID { 35 | case 1: 36 | return ErrInconsistentData 37 | case 2: 38 | return ErrNotReady 39 | case 3: 40 | return ErrNotFound 41 | case 4: 42 | return ErrAlreadyDone 43 | case 5: 44 | return ErrInvalidID 45 | case 6: 46 | return io.EOF 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-collector/collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | const maxSectors = 10 12 | 13 | var ErrTooMuchSectors = errors.New("too much sectors") 14 | 15 | type SensorValue struct { 16 | SensorID string 17 | Value float64 18 | } 19 | 20 | type Sector interface { 21 | ID() string 22 | GetSensorValues(ctx context.Context) ([]SensorValue, error) 23 | } 24 | 25 | // Collect конкурентно собирает значения датчиков с секторов, объединяет их в один 26 | // слайс данных и выдаёт наружу. 27 | // При превышении лимита на количество секторов возвращает ошибку ErrTooMuchSectors. 28 | // При возникновении ошибки во время опроса очередного сектора, функция завершает 29 | // свою работу и возвращает эту ошибку. 30 | func Collect(ctx context.Context, sectors []Sector) ([]SensorValue, error) { 31 | // Для сохранения импортов. Удали эти строки. 32 | _ = errgroup.Group{} 33 | _ = fmt.Errorf 34 | 35 | // Реализуй меня. 36 | return nil, nil 37 | } 38 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/broken-chain/chain.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | type Message struct { 9 | ID string 10 | } 11 | 12 | func ProcessMessage() error { // Почини возвращаемую цепочку ошибок! 13 | msg := readMessageFromQueue() 14 | 15 | if err := process(msg); err != nil { 16 | return fmt.Errorf("cannot process msg: %w", err) 17 | } 18 | 19 | return nil 20 | } 21 | 22 | func readMessageFromQueue() Message { 23 | return Message{ID: "8fbad38c-c5c5-11eb-b876-1e00d13a7870"} 24 | } 25 | 26 | func process(msg Message) error { 27 | if err := saveMsg(msg); err != nil { 28 | return fmt.Errorf("cannot write data: %v", err) 29 | } 30 | return nil 31 | } 32 | 33 | type saveMsgError struct { 34 | id string 35 | err error 36 | } 37 | 38 | func (w *saveMsgError) Error() string { 39 | return fmt.Sprintf("save msg %q error: %v", w.id, w.err) 40 | } 41 | 42 | func saveMsg(m Message) error { 43 | if true { 44 | return &saveMsgError{ 45 | id: m.ID, 46 | err: fmt.Errorf("%w", io.ErrShortWrite), 47 | } 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /examples/08-future-of-errors-in-go2/copy-file/v3.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func CopyFileV3(src, dst string) (err error) { 11 | r, err := os.Open(src) 12 | if err != nil { 13 | return fmt.Errorf("copy %q to %q: %v", src, dst, err) 14 | } 15 | defer r.Close() 16 | 17 | w, err := os.Create(dst) 18 | if err != nil { 19 | return fmt.Errorf("copy %q to %q: create dst file: %v", src, dst, err) 20 | } 21 | defer func() { 22 | if err := w.Close(); err != nil { 23 | log.Printf("copy %q to %q: cannot close dst file: %v", src, dst, err) 24 | } 25 | 26 | if err != nil { 27 | if err := os.Remove(dst); err != nil { 28 | log.Printf("copy %q to %q: cannot remove dst file : %v", src, dst, err) 29 | } 30 | } 31 | }() 32 | 33 | if _, err := io.Copy(w, r); err != nil { 34 | return fmt.Errorf("copy %q to %q: cannot do io.Copy: %v", src, dst, err) 35 | } 36 | 37 | if err := w.Sync(); err != nil { 38 | return fmt.Errorf("copy %q to %q: cannot sync dst file %v", src, dst, err) 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-limited/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | func main() { 11 | var eg errgroup.Group 12 | 13 | eg.SetLimit(3) 14 | 15 | for i := 0; i < 11; i++ { 16 | i := i 17 | eg.Go(func() error { 18 | work(i) 19 | return nil 20 | }) 21 | } 22 | 23 | _ = eg.Wait() 24 | } 25 | 26 | func work(i int) { 27 | log.Printf("worker %d: do hard work...\n", i) 28 | time.Sleep(3 * time.Second) 29 | } 30 | 31 | /* 32 | 2023/06/04 12:01:41 worker 1: do hard work... 33 | 2023/06/04 12:01:41 worker 2: do hard work... 34 | 2023/06/04 12:01:41 worker 0: do hard work... 35 | 2023/06/04 12:01:44 worker 3: do hard work... 36 | 2023/06/04 12:01:44 worker 4: do hard work... 37 | 2023/06/04 12:01:44 worker 5: do hard work... 38 | 2023/06/04 12:01:47 worker 8: do hard work... 39 | 2023/06/04 12:01:47 worker 7: do hard work... 40 | 2023/06/04 12:01:47 worker 6: do hard work... 41 | 2023/06/04 12:01:50 worker 10: do hard work... 42 | 2023/06/04 12:01:50 worker 9: do hard work... 43 | */ 44 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/Makefile: -------------------------------------------------------------------------------- 1 | SRC = main.c 2 | BIN = ./main.out 3 | 4 | .PHONY: run check 5 | .DEFAULT_GOAL := run 6 | 7 | build: 8 | @docker run \ 9 | --rm \ 10 | --platform linux/amd64 \ 11 | --workdir $(HOME) \ 12 | -v $(PWD):$(HOME) \ 13 | --entrypoint gcc \ 14 | karek/valgrind -Wall -g -o $(BIN) $(SRC) 15 | 16 | run: build 17 | @docker rm -f valgrind >/dev/null 2>&1 || true 18 | @docker run \ 19 | --rm \ 20 | --name valgrind \ 21 | -i \ 22 | --platform linux/amd64 \ 23 | --workdir $(HOME) \ 24 | -v $(PWD):$(HOME) \ 25 | --entrypoint valgrind \ 26 | karek/valgrind --tool=memcheck --leak-check=full $(BIN) 27 | 28 | check: build 29 | @docker rm -f valgrind >/dev/null 2>&1 || true 30 | @for user_request in ./testdata/*.json ; do \ 31 | docker run \ 32 | --rm \ 33 | --name valgrind \ 34 | -i \ 35 | --platform linux/amd64 \ 36 | --workdir $(HOME) \ 37 | -v $(PWD):$(HOME) \ 38 | --entrypoint valgrind \ 39 | karek/valgrind -q --tool=memcheck --leak-check=full $(BIN) < $$user_request ; \ 40 | done 41 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/write-resp-err-conn/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 11 | time.Sleep(3 * time.Second) 12 | 13 | data := make([]byte, 1<<20) // 1Mb 14 | logWriteErr(w.Write(data)) // write tcp [::1]:8080->[::1]:60756: write: broken pipe 15 | }) 16 | 17 | go func() { 18 | time.Sleep(3 * time.Second) // Wait for server start up. 19 | 20 | client := &http.Client{Timeout: time.Second} 21 | resp, err := client.Get("http://localhost:8080") //nolint:noctx 22 | if err != nil { 23 | // context deadline exceeded (Client.Timeout exceeded while awaiting headers) 24 | log.Println("cannot do GET: " + err.Error()) 25 | return 26 | } 27 | _ = resp.Body.Close() 28 | }() 29 | 30 | if err := http.ListenAndServe(":8080", http.DefaultServeMux); err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | 35 | func logWriteErr(_ int, err error) { 36 | if err != nil { 37 | log.Println("cannot write response: " + err.Error()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/exec-err-pain/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "text/template" 7 | ) 8 | 9 | func main() { 10 | var err error 11 | 12 | var ( 13 | t template.ExecError 14 | tPtr *template.ExecError 15 | ) 16 | switch { 17 | case errors.As(err, &t): 18 | case errors.As(err, &tPtr): 19 | } 20 | 21 | var ( 22 | n net.DNSError 23 | nPtr *net.DNSError 24 | _ = n 25 | ) 26 | switch { 27 | // case errors.As(err, &n): // Запаникует! 28 | case errors.As(err, &nPtr): 29 | } 30 | } 31 | 32 | type MyExecError struct{} 33 | 34 | func (m *MyExecError) Error() string { 35 | return "cool error" 36 | } 37 | 38 | func (m *MyExecError) Is(target error) bool { 39 | // Что выбрать? 40 | switch target.(type) { 41 | case *template.ExecError: 42 | // ... 43 | case template.ExecError: 44 | // ... 45 | } 46 | return false 47 | } 48 | 49 | func (m *MyExecError) As(target any) bool { 50 | // Что выбрать? 51 | switch target.(type) { 52 | case *template.ExecError: 53 | // ... 54 | case **template.ExecError: 55 | // ... 56 | } 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator/Makefile: -------------------------------------------------------------------------------- 1 | SRC = main.c 2 | BIN = ./main.out 3 | 4 | .PHONY: run check 5 | .DEFAULT_GOAL := run 6 | 7 | build: 8 | @docker run \ 9 | --rm \ 10 | --platform linux/amd64 \ 11 | --workdir $(HOME) \ 12 | -v $(PWD):$(HOME) \ 13 | --entrypoint gcc \ 14 | karek/valgrind -Wall -g -o $(BIN) $(SRC) 15 | 16 | run: build 17 | @docker rm -f valgrind >/dev/null 2>&1 || true 18 | @docker run \ 19 | --rm \ 20 | --name valgrind \ 21 | -i \ 22 | --platform linux/amd64 \ 23 | --workdir $(HOME) \ 24 | -v $(PWD):$(HOME) \ 25 | --entrypoint valgrind \ 26 | karek/valgrind --tool=memcheck --suppressions=allocator.suppr --leak-check=full $(BIN) 27 | 28 | check: build 29 | @docker rm -f valgrind >/dev/null 2>&1 || true 30 | @for input in ./testdata/*.txt ; do \ 31 | docker run \ 32 | --rm \ 33 | --name valgrind \ 34 | -i \ 35 | --platform linux/amd64 \ 36 | --workdir $(HOME) \ 37 | -v $(PWD):$(HOME) \ 38 | --entrypoint valgrind \ 39 | karek/valgrind -q --tool=memcheck --suppressions=allocator.suppr --leak-check=full $(BIN) < $$input ; \ 40 | done 41 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/allocator-errno_t/Makefile: -------------------------------------------------------------------------------- 1 | SRC = main.c 2 | BIN = ./main.out 3 | 4 | .PHONY: run check 5 | .DEFAULT_GOAL := run 6 | 7 | build: 8 | @docker run \ 9 | --rm \ 10 | --platform linux/amd64 \ 11 | --workdir $(HOME) \ 12 | -v $(PWD):$(HOME) \ 13 | --entrypoint gcc \ 14 | karek/valgrind -Wall -g -o $(BIN) $(SRC) 15 | 16 | run: build 17 | @docker rm -f valgrind >/dev/null 2>&1 || true 18 | @docker run \ 19 | --rm \ 20 | --name valgrind \ 21 | -i \ 22 | --platform linux/amd64 \ 23 | --workdir $(HOME) \ 24 | -v $(PWD):$(HOME) \ 25 | --entrypoint valgrind \ 26 | karek/valgrind --tool=memcheck --suppressions=allocator.suppr --leak-check=full $(BIN) 27 | 28 | check: build 29 | @docker rm -f valgrind >/dev/null 2>&1 || true 30 | @for input in ./testdata/*.txt ; do \ 31 | docker run \ 32 | --rm \ 33 | --name valgrind \ 34 | -i \ 35 | --platform linux/amd64 \ 36 | --workdir $(HOME) \ 37 | -v $(PWD):$(HOME) \ 38 | --entrypoint valgrind \ 39 | karek/valgrind -q --tool=memcheck --suppressions=allocator.suppr --leak-check=full $(BIN) < $$input ; \ 40 | done 41 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/db.h: -------------------------------------------------------------------------------- 1 | #ifndef DB_H 2 | #define DB_H 3 | 4 | #include 5 | #include 6 | 7 | #define KNOWN_USER 4224 8 | #define INTERNAL_ERR_THRESHOLD 10000 9 | 10 | typedef struct { 11 | int id; 12 | char *email; 13 | } user_t; 14 | 15 | typedef enum { 16 | DB_ERR_OK = 0, 17 | DB_ERR_INTERNAL = 1, 18 | DB_ERR_NOT_FOUND = 2, 19 | } db_error_t; 20 | 21 | db_error_t db_get_user_by_id(int uid, user_t **user) 22 | { 23 | *user = NULL; 24 | 25 | if (uid >= INTERNAL_ERR_THRESHOLD) { 26 | return DB_ERR_INTERNAL; 27 | } 28 | 29 | if (uid != KNOWN_USER) { 30 | return DB_ERR_NOT_FOUND; 31 | } 32 | 33 | user_t *u = (user_t *) malloc(sizeof(user_t)); 34 | if (u == NULL) { 35 | return DB_ERR_INTERNAL; 36 | } 37 | 38 | u->id = uid; 39 | 40 | u->email = (char *) calloc(14, sizeof(char)); 41 | strncpy(u->email, "bob@gmail.com\0", 14); 42 | if (u->email == NULL) { 43 | free(u); 44 | return DB_ERR_INTERNAL; 45 | } 46 | 47 | *user = u; 48 | return DB_ERR_OK; 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/cats-unmarshalling-err-channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type Cat struct { 10 | Name string `json:"name"` 11 | } 12 | 13 | func main() { 14 | catsJSONs := []string{`{"name": "Bobby"}`, `"name": "Billy"`, `{"name": "Васёк"}`} 15 | 16 | done := make(chan struct{}) 17 | catsCh := make(chan Cat) 18 | errsCh := make(chan error) 19 | 20 | for _, catData := range catsJSONs { 21 | go func(catData string) { // Разбираем котиков в несколько горутин. 22 | var cat Cat 23 | if err := json.Unmarshal([]byte(catData), &cat); err != nil { 24 | errsCh <- err // Случилась ошибка. 25 | return 26 | } 27 | catsCh <- cat // Всё прошло хорошо. 28 | }(catData) 29 | } 30 | 31 | var wg sync.WaitGroup 32 | wg.Add(len(catsJSONs)) 33 | 34 | go func() { 35 | wg.Wait() 36 | close(done) 37 | }() 38 | 39 | for { 40 | select { 41 | case <-done: 42 | return 43 | 44 | case c := <-catsCh: 45 | wg.Done() 46 | fmt.Println(c) 47 | 48 | case err := <-errsCh: 49 | wg.Done() 50 | fmt.Println(err) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tasks/02-c-errors-concept/developers-everyday-life/marshalers.h: -------------------------------------------------------------------------------- 1 | #ifndef MARSHALERS_H 2 | #define MARSHALERS_H 3 | 4 | #include 5 | #include 6 | #include "db.h" 7 | 8 | typedef struct { 9 | int user_id; 10 | } request_t; 11 | 12 | int unmarshal_request(char *request_data, request_t **request) 13 | { 14 | *request = NULL; 15 | 16 | request_t *r = (request_t *) malloc(sizeof(request_t)); 17 | if (r == NULL) { 18 | return -1; 19 | } 20 | 21 | if (sscanf(request_data, "{\"user_id\": %d}", &(r->user_id)) == 0) { 22 | free(r); 23 | return -1; 24 | } 25 | 26 | *request = r; 27 | return 0; 28 | } 29 | 30 | typedef struct { 31 | user_t *user; 32 | } response_t; 33 | 34 | int marshal_response(response_t response, char **response_data) 35 | { 36 | char *buf = (char *) calloc(100, sizeof(char)); 37 | 38 | if (sprintf(buf, "{\"user\": {\"id\": \"%d\", \"email\": \"%s\"}", response.user->id, response.user->email) == 0) { 39 | free(buf); 40 | *response_data = NULL; 41 | return -1; 42 | } 43 | 44 | *response_data = buf; 45 | return 0; 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/wrapper/wrapper_example_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func ExampleNewUserError() { 11 | err := errors.Wrap(NewUserError(io.EOF, "Bob"), "message") 12 | 13 | type withUserID interface { 14 | UserID() string 15 | } 16 | 17 | var i withUserID 18 | if errors.As(err, &i) { 19 | fmt.Println(i.UserID()) 20 | } 21 | 22 | if i, ok := errors.Cause(err).(withUserID); ok { // Это не работает! 23 | fmt.Println(i.UserID()) 24 | } 25 | 26 | // Output: 27 | // Bob 28 | } 29 | 30 | func NewUserError(err error, userID string) error { 31 | return &userError{ 32 | err: err, 33 | userID: userID, 34 | } 35 | } 36 | 37 | var _ Wrapper = (*userError)(nil) 38 | 39 | type userError struct { 40 | err error 41 | userID string 42 | } 43 | 44 | func (ie *userError) Error() string { 45 | return fmt.Sprintf("user %s: %v", ie.userID, ie.err) 46 | } 47 | 48 | func (ie *userError) Cause() error { 49 | return ie.err 50 | } 51 | 52 | func (ie *userError) Unwrap() error { 53 | return ie.err 54 | } 55 | 56 | func (ie *userError) UserID() string { 57 | return ie.userID 58 | } 59 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | # https://taskfile.dev/#/installation 2 | version: '3' 3 | 4 | silent: true 5 | 6 | tasks: 7 | default: 8 | cmds: 9 | - task: tools:install 10 | - task: tidy 11 | - task: fmt 12 | - task: lint 13 | 14 | tools:install: 15 | - echo "Install local tools..." 16 | - (which gci > /dev/null) || GO111MODULE=off go install github.com/daixiang0/gci@latest 17 | - (which gofumpt > /dev/null) || GO111MODULE=off go install mvdan.cc/gofumpt@latest 18 | 19 | tidy: 20 | cmds: 21 | - echo "Tidy..." 22 | - go mod tidy 23 | 24 | fmt: 25 | cmds: 26 | - echo "Fmt..." 27 | - gofumpt -w . 28 | - gci write -s standard -s default -s "Prefix(github.com/golang-ninja-courses/error-handling-mastery)" . 2> /dev/null 29 | 30 | lint: 31 | cmds: 32 | - task: lint:examples 33 | - task: lint:tasks 34 | 35 | lint:examples: 36 | - echo "Lint examples..." 37 | - golangci-lint run --build-tags pkg,pkg.msg.stack ./examples/... 38 | 39 | lint:tasks: 40 | - echo "Lint tasks..." 41 | - golangci-lint run ./tasks/... 42 | 43 | lint:dir: 44 | - echo "Lint..." 45 | - golangci-lint run {{.CLI_ARGS}} 46 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-unwrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/cockroachdb/errors" 8 | ) 9 | 10 | type isTemporary interface { 11 | Temporary() bool 12 | } 13 | 14 | // IsTemporaryOnce считает цепочку ошибок временной, если хотя бы одна 15 | // из ошибок в ней была временной. 16 | func IsTemporaryOnce(err error) bool { 17 | for c := err; c != nil; c = errors.UnwrapOnce(c) { 18 | e, ok := c.(isTemporary) 19 | if ok && e.Temporary() { 20 | return true 21 | } 22 | } 23 | return false 24 | } 25 | 26 | // IsTemporary считает цепочку ошибок временной, только если 27 | // корневая ошибка в ней была временной. 28 | func IsTemporary(err error) bool { 29 | c := errors.UnwrapAll(err) 30 | e, ok := c.(isTemporary) 31 | return ok && e.Temporary() 32 | } 33 | 34 | func main() { 35 | dnsErr := &net.DNSError{IsTemporary: true} // Произошла сетевая ошибка. 36 | 37 | err := fmt.Errorf("second wrap: %w", 38 | fmt.Errorf("first wrap: %w", dnsErr)) 39 | 40 | fmt.Printf("is temporary: %t\n", IsTemporaryOnce(err)) // is temporary: true 41 | fmt.Printf("is temporary at root: %t\n", IsTemporary(err)) // is temporary at root: true 42 | } 43 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/user-registration-request-multierr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "strings" 9 | 10 | "go.uber.org/multierr" 11 | ) 12 | 13 | type UserRegistrationRequest struct { 14 | err error 15 | Email string `json:"email"` 16 | Password string `json:"password"` 17 | } 18 | 19 | func (u *UserRegistrationRequest) Err() error { 20 | return u.err 21 | } 22 | 23 | func (u *UserRegistrationRequest) Unmarshal(r io.Reader) { 24 | u.err = multierr.Append(u.err, json.NewDecoder(r).Decode(u)) 25 | } 26 | 27 | func (u *UserRegistrationRequest) ValidateEmail() { 28 | if u.Email == "" { 29 | u.err = multierr.Append(u.err, errors.New("empty email")) 30 | } 31 | } 32 | 33 | func (u *UserRegistrationRequest) ValidatePassword() { 34 | if u.Password == "" { 35 | u.err = multierr.Append(u.err, errors.New("empty password")) 36 | } 37 | } 38 | 39 | func main() { 40 | var req UserRegistrationRequest 41 | req.Unmarshal(strings.NewReader(`{"email":"bob@gmail.com","password":"`)) 42 | req.ValidateEmail() 43 | req.ValidatePassword() 44 | 45 | fmt.Printf("%#v", req.Err()) // unexpected EOF; empty email; empty password 46 | } 47 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/concurrent-event-publishing/job.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | //go:generate mockgen -source=$GOFILE -destination=job_mocks_test.go -package=job 11 | 12 | type eventStream interface { 13 | Publish(ctx context.Context, userID string, event string) error 14 | } 15 | 16 | type Job struct { 17 | eventStream eventStream 18 | } 19 | 20 | func (j *Job) Handle(ctx context.Context, payload string) error { 21 | _, err := parsePayload(payload) 22 | if err != nil { 23 | return fmt.Errorf("parse payload: %v", err) 24 | } 25 | 26 | wg, ctx := errgroup.WithContext(ctx) 27 | 28 | wg.Go(func() error { 29 | err = j.eventStream.Publish(ctx, "user-id-1", "some event") 30 | if err != nil { 31 | return fmt.Errorf("publish first event: %v", err) 32 | } 33 | return nil 34 | }) 35 | 36 | wg.Go(func() error { 37 | err = j.eventStream.Publish(ctx, "user-id-2", "another yet event") 38 | if err != nil { 39 | return fmt.Errorf("publish second event: %v", err) 40 | } 41 | return nil 42 | }) 43 | 44 | return wg.Wait() 45 | } 46 | 47 | func parsePayload(_ string) (any, error) { 48 | return nil, nil 49 | } 50 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/broken-chain/chain_test.go: -------------------------------------------------------------------------------- 1 | package chain 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func ExampleProcessMessage() { 14 | if errors.Is(ProcessMessage(), io.ErrShortWrite) { 15 | fmt.Println("chain is not broken") 16 | } else { 17 | fmt.Println("chain is broken") 18 | } 19 | 20 | // Output: 21 | // chain is not broken 22 | } 23 | 24 | func TestProcessMessage(t *testing.T) { 25 | err := ProcessMessage() 26 | require.Error(t, err) 27 | assert.ErrorIs(t, err, io.ErrShortWrite) 28 | assert.NotErrorIs(t, err, io.EOF) 29 | assert.EqualError(t, err, 30 | `cannot process msg: cannot write data: save msg "8fbad38c-c5c5-11eb-b876-1e00d13a7870" error: short write`) 31 | } 32 | 33 | type EOFBrotherError struct{} // 34 | func (*EOFBrotherError) Error() string { return "EOF" } 35 | func (*EOFBrotherError) Is(err error) bool { return err == io.EOF } 36 | 37 | func Test_saveMsgError_IsInTheChain(t *testing.T) { 38 | err := fmt.Errorf("wrap 2: %w", &saveMsgError{ 39 | err: fmt.Errorf("wrap 1: %w", new(EOFBrotherError)), 40 | }) 41 | assert.ErrorIs(t, err, io.EOF) 42 | } 43 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/user-registration-request-monad-2/user_registration_request.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "strings" 9 | ) 10 | 11 | type UserRegistrationRequest struct { 12 | err error 13 | Email string `json:"email"` 14 | Password string `json:"password"` 15 | } 16 | 17 | func (u *UserRegistrationRequest) Err() error { 18 | return u.err 19 | } 20 | 21 | func (u *UserRegistrationRequest) Unmarshal(r io.Reader) { 22 | if u.err != nil { 23 | return 24 | } 25 | u.err = json.NewDecoder(r).Decode(u) 26 | } 27 | 28 | func (u *UserRegistrationRequest) ValidateEmail() { 29 | if u.err != nil { 30 | return 31 | } 32 | 33 | if u.Email == "" { 34 | u.err = errors.New("empty email") 35 | } 36 | } 37 | 38 | func (u *UserRegistrationRequest) ValidatePassword() { 39 | if u.err != nil { 40 | return 41 | } 42 | 43 | if u.Password == "" { 44 | u.err = errors.New("empty password") 45 | } 46 | } 47 | 48 | func main() { 49 | var req UserRegistrationRequest 50 | req.Unmarshal(strings.NewReader(`{"email":"bob@gmail.com","password":""}`)) 51 | req.ValidateEmail() 52 | req.ValidatePassword() 53 | 54 | fmt.Println(req.Err()) // empty password 55 | } 56 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/api-borders/db_user_test.go: -------------------------------------------------------------------------------- 1 | package db_test 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | 12 | db "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/api-borders" 13 | ) 14 | 15 | func TestGetUserByIDOriginal(t *testing.T) { 16 | _, err := db.GetUserByIDOriginal(context.Background(), "uid") 17 | require.Error(t, err) 18 | assert.ErrorIs(t, err, sql.ErrNoRows) // Прыгаем через слои. 19 | } 20 | 21 | func TestGetUserByIDOwnError(t *testing.T) { 22 | _, err := db.GetUserByIDOwnError(context.Background(), "uid") 23 | require.Error(t, err) 24 | assert.ErrorIs(t, err, db.ErrNotFound) 25 | assert.False(t, errors.Is(err, sql.ErrNoRows)) 26 | } 27 | 28 | func TestIsNotFoundError(t *testing.T) { 29 | _, err := db.GetUserByIDOriginal(context.Background(), "uid") 30 | require.Error(t, err) 31 | assert.True(t, db.IsNotFoundError(err)) 32 | } 33 | 34 | func TestIsNotFoundErrorPrivate(t *testing.T) { 35 | _, err := db.GetUserByIDOwnPrivateError(context.Background(), "uid") 36 | require.Error(t, err) 37 | assert.True(t, db.IsNotFoundError2(err)) 38 | } 39 | -------------------------------------------------------------------------------- /examples/06-working-with-errors-in-tests/testify-gotchas/example_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestEqualErrors(t *testing.T) { 14 | MyEOF := errors.New(io.EOF.Error()) 15 | require.Equal(t, MyEOF, io.EOF) // Хотелось бы, чтобы тест не прошёл. 16 | } 17 | 18 | func TestErrorInsteadOfErrorIs(t *testing.T) { 19 | someOperation := func() error { 20 | // Попробуйте: 21 | // return nil 22 | return io.EOF 23 | } 24 | 25 | err := someOperation() 26 | require.Error(t, err, context.DeadlineExceeded) // Хотелось бы, чтобы тест не прошёл. 27 | } 28 | 29 | func TestErrorIsInvalidOrder(t *testing.T) { 30 | errExpected := io.EOF 31 | err := fmt.Errorf("err: %w", io.EOF) 32 | require.ErrorIs(t, errExpected, err) // Хотелось бы, чтобы тест прошёл. 33 | } 34 | 35 | func TestErrorIsAtHome(t *testing.T) { 36 | someOperation := func() error { 37 | return io.EOF 38 | } 39 | 40 | err := someOperation() 41 | // Обратите внимание на сообщение об ошибке! 42 | // Без него будет сложно понять, почему errors.Is вернул false. 43 | require.True(t, errors.Is(err, context.DeadlineExceeded), "actual err: %v", err) 44 | } 45 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-error-types/handler_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestHandler_Handle(t *testing.T) { 13 | cases := []struct { 14 | job Job 15 | expectedErr error 16 | expectedPostpone time.Duration 17 | }{ 18 | { 19 | job: Job{ID: 0}, 20 | expectedErr: nil, 21 | }, 22 | { 23 | job: Job{ID: 1}, 24 | expectedErr: nil, 25 | }, 26 | { 27 | job: Job{ID: 2}, 28 | expectedErr: nil, 29 | expectedPostpone: time.Second, 30 | }, 31 | { 32 | job: Job{ID: 3}, 33 | expectedErr: nil, 34 | }, 35 | { 36 | job: Job{ID: 4}, 37 | expectedErr: nil, 38 | }, 39 | { 40 | job: Job{ID: 5}, 41 | expectedErr: nil, 42 | }, 43 | { 44 | job: Job{ID: 6}, 45 | expectedErr: io.EOF, 46 | }, 47 | } 48 | 49 | for _, tt := range cases { 50 | t.Run(fmt.Sprintf("handle job #%d", tt.job.ID), func(t *testing.T) { 51 | var h Handler 52 | p, err := h.Handle(tt.job) 53 | assert.Equal(t, tt.expectedErr, err) 54 | assert.Equal(t, tt.expectedPostpone, p) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-grpc/main_test.go: -------------------------------------------------------------------------------- 1 | package cockroachgrpc 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "os" 7 | "testing" 8 | 9 | "github.com/cockroachdb/errors/grpc/middleware" 10 | "github.com/hydrogen18/memlistener" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | var Client EchoerClient 15 | 16 | func TestMain(m *testing.M) { 17 | srv := &EchoServer{} 18 | 19 | lis := memlistener.NewMemoryListener() 20 | 21 | grpcServer := grpc.NewServer(grpc.UnaryInterceptor(middleware.UnaryServerInterceptor)) 22 | RegisterEchoerServer(grpcServer, srv) 23 | 24 | go func() { 25 | if err := grpcServer.Serve(lis); err != nil { 26 | panic(err) 27 | } 28 | }() 29 | 30 | dialOpts := []grpc.DialOption{ 31 | grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { 32 | return lis.Dial("", "") 33 | }), 34 | grpc.WithInsecure(), 35 | grpc.WithUnaryInterceptor(middleware.UnaryClientInterceptor), 36 | } 37 | 38 | clientConn, err := grpc.Dial("", dialOpts...) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | Client = NewEchoerClient(clientConn) 44 | 45 | code := m.Run() 46 | 47 | grpcServer.Stop() 48 | if err := clientConn.Close(); err != nil { 49 | panic(err) 50 | } 51 | 52 | os.Exit(code) 53 | } 54 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handmade-stack/handler.go: -------------------------------------------------------------------------------- 1 | package handmadestack 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrExecSQL = errors.New("exec sql error") 9 | ErrInitTransaction = errors.New("init transaction error") 10 | ) 11 | 12 | type Entity struct { 13 | ID string 14 | } 15 | 16 | // Используются тестами. 17 | var ( 18 | getEntity = func() (Entity, error) { return Entity{ID: "some-id"}, nil } 19 | updateEntity = func(e Entity) error { return nil } 20 | runInTransaction = func(f func() error) error { return f() } 21 | ) 22 | 23 | // Перепиши меня так, чтобы логика сохранилась, 24 | // но путь до каждой ошибки был очевиден. 25 | func handler() (Entity, error) { 26 | var e Entity 27 | 28 | if err := runInTransaction(func() (opErr error) { 29 | e, opErr = getEntity() 30 | if opErr != nil { 31 | return opErr 32 | } 33 | 34 | return updateEntity(e) 35 | }); err != nil { 36 | return Entity{}, err 37 | } 38 | 39 | if err := runInTransaction(func() error { 40 | return updateEntity(e) 41 | }); err != nil { 42 | return Entity{}, err 43 | } 44 | 45 | if err := runInTransaction(func() (opErr error) { 46 | return updateEntity(e) 47 | }); err != nil { 48 | return Entity{}, err 49 | } 50 | 51 | return e, nil 52 | } 53 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/file-load-err/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | type FileLoadError struct { 9 | URL string 10 | Err error // Для хранения "родительской" ошибки. 11 | } 12 | 13 | func (p *FileLoadError) Error() string { 14 | // Текст "родительской ошибки" фигурирует в тексте этой ошибки. 15 | return fmt.Sprintf("%q: %v", p.URL, p.Err) 16 | } 17 | 18 | type File struct{} 19 | 20 | func getFile(u string) (File, error) { 21 | return File{}, context.Canceled 22 | } 23 | 24 | func loadFiles(urls ...string) ([]File, error) { 25 | files := make([]File, len(urls)) 26 | for i, url := range urls { 27 | f, err := getFile(url) 28 | if err != nil { 29 | return nil, &FileLoadError{url, err} // <- Врапим ошибку загрузки в *FileLoadError. 30 | } 31 | files[i] = f 32 | } 33 | return files, nil 34 | } 35 | 36 | func transfer() error { 37 | _, err := loadFiles("www.golang-ninja.ru") 38 | if err != nil { 39 | return fmt.Errorf("cannot load files: %v", err) 40 | } 41 | 42 | // ... 43 | return nil 44 | } 45 | 46 | func main() { 47 | if err := transfer(); err != nil { 48 | if _, ok := err.(*FileLoadError); ok { 49 | fmt.Println("file load err received") 50 | } else { 51 | fmt.Println("unexpected error") 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/file-load-err/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | type FileLoadError struct { 9 | URL string 10 | Err error // Для хранения "родительской" ошибки. 11 | } 12 | 13 | func (p *FileLoadError) Error() string { 14 | // Текст "родительской ошибки" фигурирует в тексте этой ошибки. 15 | return fmt.Sprintf("%q: %v", p.URL, p.Err) 16 | } 17 | 18 | type File struct{} 19 | 20 | func getFile(u string) (File, error) { 21 | return File{}, context.Canceled 22 | } 23 | 24 | func loadFiles(urls ...string) ([]File, error) { 25 | files := make([]File, len(urls)) 26 | for i, url := range urls { 27 | f, err := getFile(url) 28 | if err != nil { 29 | return nil, &FileLoadError{url, err} // <- Врапим ошибку загрузки в *FileLoadError. 30 | } 31 | files[i] = f 32 | } 33 | return files, nil 34 | } 35 | 36 | func transfer() error { 37 | _, err := loadFiles("www.golang-ninja.ru") 38 | if err != nil { 39 | return fmt.Errorf("cannot load files: %v", err) 40 | } 41 | 42 | // ... 43 | return nil 44 | } 45 | 46 | func handle() error { 47 | if err := transfer(); err != nil { 48 | return fmt.Errorf("cannot transfer files: %v", err) 49 | } 50 | 51 | // ... 52 | return nil 53 | } 54 | 55 | func main() { 56 | fmt.Println(handle()) 57 | } 58 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/gotcha-err-iface-3/ops_test.go: -------------------------------------------------------------------------------- 1 | package ops 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func ExampleHandle() { 12 | if err := Handle(successOperation{}); err != nil { 13 | fmt.Println(err) 14 | } else { 15 | fmt.Println("no operations errors") 16 | } 17 | 18 | // Output: 19 | // no operations errors 20 | } 21 | 22 | func TestHandle(t *testing.T) { 23 | t.Run("no operations - no errors", func(t *testing.T) { 24 | err := Handle() 25 | assert.NoError(t, err) 26 | }) 27 | 28 | t.Run("all operations are success - no errors", func(t *testing.T) { 29 | err := Handle(successOperation{}, successOperation{}) 30 | assert.NoError(t, err) 31 | }) 32 | 33 | t.Run("has error operations", func(t *testing.T) { 34 | err := Handle(successOperation{}, errOperation{}, errOperation{}, successOperation{}) 35 | assert.Error(t, err) 36 | 37 | var opsErrs OperationsErrors 38 | assert.ErrorAs(t, err, &opsErrs) 39 | assert.Len(t, opsErrs, 2) 40 | }) 41 | } 42 | 43 | type successOperation struct{} 44 | 45 | func (successOperation) Do() error { 46 | return nil 47 | } 48 | 49 | type errOperation struct{} 50 | 51 | func (errOperation) Do() error { 52 | return errors.New("something wrong") 53 | } 54 | -------------------------------------------------------------------------------- /examples/05-errors-best-practices/constant-errors-diff-pkgs/main.go: -------------------------------------------------------------------------------- 1 | //nolint:gocritic,ineffassign,staticcheck,wastedassign 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/constant-errors-diff-pkgs/pkga" 8 | "github.com/golang-ninja-courses/error-handling-mastery/examples/05-errors-best-practices/constant-errors-diff-pkgs/pkgb" 9 | ) 10 | 11 | func main() { 12 | fmt.Println(pkga.ErrUnknownData == pkgb.ErrUnknownData) // true - created by global type 13 | 14 | // invalid operation: pkga.ErrInvalidHost == pkgb.ErrInvalidHost (mismatched types pkga.err and pkgb.err) 15 | // fmt.Println(pkga.ErrInvalidHost == pkgb.ErrInvalidHost) 16 | 17 | fmt.Println(error(pkga.ErrInvalidHost) == error(pkga.ErrInvalidHost)) // true - one type 18 | fmt.Println(error(pkga.ErrInvalidHost) == error(pkgb.ErrInvalidHost)) // false - diff types 19 | 20 | if err := foo(); err != nil { 21 | fmt.Println(err) // invalid host 22 | } 23 | 24 | // cannot use foo() (type error) as type pkga.err in assignment: need type assertion 25 | // err := pkga.ErrInvalidHost 26 | // err = foo() 27 | 28 | var err error = pkga.ErrInvalidHost 29 | err = foo() 30 | _ = err 31 | } 32 | 33 | func foo() error { 34 | return pkga.ErrInvalidHost 35 | } 36 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/cats-unmarshalling-err-container/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | type Cat struct { 10 | Name string `json:"name"` 11 | } 12 | 13 | type CatContainer struct { 14 | Cat Cat 15 | Err error // Сюда складываем ошибку, если что-то пошло не так. 16 | } 17 | 18 | func main() { 19 | catsJSONs := []string{`{"name": "Bobby"}`, `"name": "Billy"`, `{"name": "Васёк"}`} 20 | catsCh := make(chan CatContainer) 21 | 22 | var wg sync.WaitGroup 23 | wg.Add(len(catsJSONs)) 24 | 25 | for _, catData := range catsJSONs { 26 | go func(catData string) { 27 | defer wg.Done() 28 | 29 | var cat Cat 30 | if err := json.Unmarshal([]byte(catData), &cat); err != nil { 31 | catsCh <- CatContainer{Err: err} // Случилась ошибка. 32 | return 33 | } 34 | catsCh <- CatContainer{Cat: cat} // Всё прошло хорошо. 35 | }(catData) 36 | } 37 | 38 | go func() { 39 | wg.Wait() 40 | close(catsCh) 41 | }() 42 | 43 | for catContainer := range catsCh { 44 | if catContainer.Err != nil { 45 | fmt.Println("ERROR:", catContainer.Err) 46 | continue 47 | } 48 | fmt.Println(catContainer.Cat) 49 | } 50 | } 51 | 52 | /* 53 | ERROR: invalid character ':' after top-level value 54 | {Васёк} 55 | {Bobby} 56 | */ 57 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/handmade-stacktrace/stack_trace_test.go: -------------------------------------------------------------------------------- 1 | package stacktrace 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | // NOTE: Не редактируйте данный файл, чтобы не сбить пример и тесты ниже. 13 | 14 | func ExampleTrace() { 15 | stack := Trace() 16 | fmt.Println(stack[:1]) 17 | 18 | // Output: 19 | // handmade-stacktrace.ExampleTrace 20 | // handmade-stacktrace/stack_trace_test.go:15 21 | } 22 | 23 | func TestTrace(t *testing.T) { 24 | t.Run("simple call", func(t *testing.T) { 25 | frames := Trace() 26 | // 3 = handmade-stacktrace.TestTrace.func1 + testing.tRunner + runtime.goexit 27 | require.Len(t, frames, 3) 28 | 29 | re := regexp.MustCompile(`handmade-stacktrace\.TestTrace\.func1 30 | handmade-stacktrace\/stack_trace_test\.go:25 31 | testing\.tRunner 32 | testing\/testing\.go:\d{1,4}`) 33 | trace := frames[:2].String() 34 | assert.True(t, re.MatchString(trace), trace) 35 | }) 36 | 37 | t.Run("depth = 100", func(t *testing.T) { 38 | frames := dive(100) // На самом деле глубина чуть больше. 39 | assert.Len(t, frames, 32) 40 | }) 41 | } 42 | 43 | func dive(depth int) StackTrace { 44 | if depth == 0 { 45 | return Trace() 46 | } 47 | return dive(depth - 1) 48 | } 49 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-is-any-bug/main.go: -------------------------------------------------------------------------------- 1 | // https://github.com/cockroachdb/errors/issues/97 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/cockroachdb/errors" 8 | ) 9 | 10 | type SimpleWrapper struct { 11 | err error 12 | } 13 | 14 | func (w SimpleWrapper) Error() string { 15 | return "boom!" 16 | } 17 | 18 | func (w SimpleWrapper) Unwrap() error { 19 | return w.err 20 | } 21 | 22 | func main() { 23 | stack := errors.WithStack 24 | 25 | ref := stack(stack(SimpleWrapper{})) 26 | err := stack(stack(SimpleWrapper{err: stack(errors.New("boom!"))})) 27 | 28 | if errors.IsAny(err, ref) { 29 | fmt.Println("gotcha!") 30 | } 31 | 32 | /* panic: runtime error: index out of range [3] with length 3 33 | 34 | goroutine 1 [running]: 35 | github.com/cockroachdb/errors/markers.equalMarks(...) 36 | github.com/cockroachdb/errors@v1.9.0/markers/markers.go:205 37 | github.com/cockroachdb/errors/markers.IsAny({0x102802528, 0x1400000e438}, {0x14000167f48, 0x1, 0x14000167f28?}) 38 | github.com/cockroachdb/errors@v1.9.0/markers/markers.go:186 +0x364 39 | github.com/cockroachdb/errors.IsAny(...) 40 | github.com/cockroachdb/errors@v1.9.0/markers_api.go:64 41 | main.main() 42 | examples/04-non-standard-modules/cockroach-is-any-bug/main.go:26 +0x318 43 | */ 44 | } 45 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/safe-factorial/factorial_test.go: -------------------------------------------------------------------------------- 1 | package factorial 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCalculate(t *testing.T) { 10 | cases := []struct { 11 | name string 12 | n int 13 | expectedErr error 14 | expectedErrContains string 15 | expectedFactorial int 16 | }{ 17 | { 18 | name: "negative n", 19 | n: -1, 20 | expectedErr: ErrNegativeN, 21 | }, 22 | { 23 | name: "too deep", 24 | n: 1e3, 25 | expectedErr: ErrTooDeep, 26 | expectedErrContains: "256", 27 | }, 28 | { 29 | name: "factorial of 0 is 1", 30 | n: 0, 31 | expectedFactorial: 1, 32 | }, 33 | { 34 | name: "factorial of 10 is 3628800", 35 | n: 10, 36 | expectedFactorial: 3628800, 37 | }, 38 | } 39 | 40 | for _, tt := range cases { 41 | t.Run(tt.name, func(t *testing.T) { 42 | f, err := Calculate(tt.n) 43 | require.ErrorIs(t, err, tt.expectedErr) 44 | if tt.expectedErrContains != "" { 45 | require.Error(t, err) 46 | require.Contains(t, err.Error(), tt.expectedErrContains) 47 | } 48 | require.Equal(t, tt.expectedFactorial, f) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/cancellation-cause/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/signal" 9 | "time" 10 | ) 11 | 12 | var ( 13 | errProgramInterrupted = errors.New("program interrupted") 14 | errOrchestrationTimeout = errors.New("orchestration timeout") 15 | errWorkTimeout = errors.New("work timeout") 16 | ) 17 | 18 | func main() { 19 | ctx, cancel := context.WithCancelCause(context.Background()) 20 | defer cancel(nil) 21 | 22 | go func() { 23 | ch := make(chan os.Signal, 1) 24 | signal.Notify(ch, os.Interrupt) 25 | <-ch 26 | cancel(errProgramInterrupted) 27 | }() 28 | 29 | err := orchestrate(ctx) 30 | fmt.Println(err) 31 | // aborting work: context canceled, because of program interrupted 32 | // or 33 | // aborting work: context deadline exceeded, because of orchestration timeout 34 | } 35 | 36 | func orchestrate(ctx context.Context) error { 37 | ctx, cancel := context.WithTimeoutCause(ctx, 3*time.Second, errOrchestrationTimeout) 38 | defer cancel() 39 | return doWork(ctx) 40 | } 41 | 42 | func doWork(ctx context.Context) error { 43 | ctx, cancel := context.WithTimeoutCause(ctx, 10*time.Second, errWorkTimeout) 44 | defer cancel() 45 | 46 | <-ctx.Done() 47 | return fmt.Errorf("aborting work: %v, because of %w", ctx.Err(), context.Cause(ctx)) 48 | } 49 | -------------------------------------------------------------------------------- /tasks/04-non-standard-modules/trim-stacktrace/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func ExampleTrimStackTrace() { 15 | err := errors.Wrap(errors.WithStack(io.EOF), "read file") 16 | fmt.Printf("%+v", TrimStackTrace(err)) 17 | 18 | // Output: 19 | // read file: EOF 20 | } 21 | 22 | func TestTrimStackTrace(t *testing.T) { 23 | cases := []struct { 24 | name string 25 | err error 26 | }{ 27 | { 28 | name: "pkg/errors", 29 | err: errors.Wrapf(errors.Wrap(errors.WithStack(io.EOF), "message 1"), "message %d", 2), 30 | }, 31 | { 32 | name: "std errors", 33 | err: fmt.Errorf("message %d: %w", 2, fmt.Errorf("message 1: %w", io.EOF)), 34 | }, 35 | } 36 | 37 | for _, tt := range cases { 38 | t.Run(tt.name, func(t *testing.T) { 39 | trimmedErr := TrimStackTrace(tt.err) 40 | 41 | b := bytes.NewBuffer(nil) 42 | _, err := fmt.Fprintf(b, "%+v", trimmedErr) 43 | require.NoError(t, err) 44 | assert.ErrorIs(t, trimmedErr, io.EOF) 45 | assert.Equal(t, "message 2: message 1: EOF", b.String()) 46 | }) 47 | } 48 | } 49 | 50 | func TestTrimStackTrace_Nil(t *testing.T) { 51 | err := TrimStackTrace(nil) 52 | assert.Nil(t, err) 53 | } 54 | -------------------------------------------------------------------------------- /examples/02-c-errors-concept/validation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef enum { 6 | VALID_ERR_OK = 0, 7 | VALID_ERR_INVALID_USERNAME, 8 | VALID_ERR_INVALID_EMAIL, 9 | VALID_ERR_WEAK_PASSWORD, 10 | VALID_ERR_COUNT, // Служебное поле для определения размера enum. 11 | } validation_error_t; 12 | 13 | const char* const VALIDATION_ERROR_STRS[] = { 14 | "Everything is OK", 15 | "Invalid username", 16 | "Invalid email", 17 | "Too weak password", 18 | }; 19 | 20 | const char* validation_error_str(validation_error_t err) 21 | { 22 | if (VALID_ERR_OK <= err && err < VALID_ERR_COUNT) { 23 | return VALIDATION_ERROR_STRS[err]; 24 | } 25 | return "Unknown"; 26 | } 27 | 28 | const int PASS_MIN_LEN = 10; 29 | 30 | typedef struct { 31 | char *username; 32 | char *email; 33 | char *password; 34 | } user_t; 35 | 36 | validation_error_t validate(user_t u) 37 | { 38 | if (strlen(u.password) < PASS_MIN_LEN) { 39 | return VALID_ERR_WEAK_PASSWORD; 40 | } 41 | // ... 42 | return 0; 43 | } 44 | 45 | int main() 46 | { 47 | validation_error_t err = validate((user_t){"bob", "bob@gmail.com", "bob123"}); 48 | if (err != 0) { 49 | printf("user validation err: %s", validation_error_str(err)); 50 | } 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/http-error/http_error_test.go: -------------------------------------------------------------------------------- 1 | package httperr 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestHTTPError(t *testing.T) { 12 | cases := []struct { 13 | err error 14 | code int 15 | text string 16 | }{ 17 | { 18 | err: ErrStatusOK, 19 | code: http.StatusOK, 20 | text: "200 OK", 21 | }, 22 | { 23 | err: ErrStatusBadRequest, 24 | code: http.StatusBadRequest, 25 | text: "400 Bad Request", 26 | }, 27 | { 28 | err: ErrStatusNotFound, 29 | code: http.StatusNotFound, 30 | text: "404 Not Found", 31 | }, 32 | { 33 | err: ErrStatusUnprocessableEntity, 34 | code: http.StatusUnprocessableEntity, 35 | text: "422 Unprocessable Entity", 36 | }, 37 | { 38 | err: ErrStatusInternalServerError, 39 | code: http.StatusInternalServerError, 40 | text: "500 Internal Server Error", 41 | }, 42 | } 43 | 44 | for _, tt := range cases { 45 | _, ok := tt.err.(HTTPError) 46 | assert.True(t, ok) 47 | 48 | var c interface { 49 | Code() int 50 | } 51 | require.ErrorAs(t, tt.err, &c) 52 | assert.Equal(t, tt.code, c.Code()) 53 | assert.Equal(t, tt.err.Error(), tt.text) 54 | } 55 | } 56 | 57 | func TestHTTPErrorIsInt(t *testing.T) { 58 | _ = HTTPError(200) 59 | } 60 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/cockroach-combine-errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/cockroachdb/errors" 10 | ) 11 | 12 | const google = "https://www.google.com" 13 | 14 | func GetPage(ctx context.Context, url string) (data []byte, err error) { 15 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 16 | if err != nil { 17 | return nil, errors.WithMessage(err, "create req") 18 | } 19 | 20 | response, err := http.DefaultClient.Do(req) 21 | if err != nil { 22 | return nil, errors.WithMessage(err, "do request") 23 | } 24 | defer func() { 25 | // Дополняем основную ошибку второстепенной, если она возникнет. 26 | // При этом, если есть только второстепенная ошибка, то она и будет возвращена. 27 | // 28 | // ВАЖНО: использование именованного возвращаемого значения. 29 | err = errors.CombineErrors(err, response.Body.Close()) 30 | }() 31 | 32 | data, err = ioutil.ReadAll(response.Body) 33 | if err != nil { 34 | return nil, errors.WithMessage(err, "read body") 35 | } 36 | return data, nil 37 | } 38 | 39 | func main() { 40 | ctx, cancel := context.WithCancel(context.Background()) 41 | defer cancel() 42 | 43 | d, err := GetPage(ctx, google) 44 | if err != nil { 45 | fmt.Printf("%+v", err) 46 | return 47 | } 48 | fmt.Println(string(d)) 49 | } 50 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/idempotent-wrap/wrap_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func ExampleWrap() { 13 | err := Wrap(Wrap(Wrap(io.EOF, "wrap 0"), "wrap 1"), "wrap 2") 14 | fmt.Printf("%+v", err) 15 | } 16 | 17 | func TestWrap(t *testing.T) { 18 | const wraps = 100 19 | 20 | t.Run("single wrap", func(t *testing.T) { 21 | err := Wrap(io.EOF, "wrap 0") 22 | traces := getTracesCount(err) 23 | assert.Equal(t, 1, traces) 24 | }) 25 | 26 | t.Run("use wrap only", func(t *testing.T) { 27 | err := io.EOF 28 | for i := 0; i < wraps; i++ { 29 | err = Wrap(err, fmt.Sprintf("wrap %d", i)) 30 | if i == 0 { 31 | assert.Implements(t, (*stackTracer)(nil), err) 32 | } 33 | } 34 | 35 | traces := getTracesCount(err) 36 | assert.Equal(t, 1, traces) 37 | }) 38 | 39 | t.Run("wrapped error already has stack", func(t *testing.T) { 40 | err := errors.WithStack(io.EOF) 41 | for i := 0; i < wraps; i++ { 42 | err = Wrap(err, fmt.Sprintf("wrap %d", i)) 43 | } 44 | 45 | traces := getTracesCount(err) 46 | assert.Equal(t, 1, traces) 47 | }) 48 | } 49 | 50 | func getTracesCount(err error) (n int) { 51 | for e := err; e != nil; e = errors.Unwrap(e) { 52 | if _, ok := e.(stackTracer); ok { 53 | n++ 54 | } 55 | } 56 | return n 57 | } 58 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/emperror-panic-and-recover/client-server/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | const ( 15 | port = 8888 16 | shutdownTimeout = time.Second 17 | ) 18 | 19 | func main() { 20 | mux := http.DefaultServeMux 21 | mux.Handle("/", http.HandlerFunc(Handle)) 22 | 23 | server := &http.Server{ 24 | Addr: fmt.Sprintf("127.0.0.1:%d", port), 25 | Handler: NewPanicMiddleware(mux), 26 | } 27 | 28 | errs := make(chan error) 29 | signals := make(chan os.Signal, 1) 30 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 31 | 32 | go listen(errs, server) 33 | 34 | select { 35 | case err := <-errs: 36 | log.Println("error happened:", err) 37 | _ = shutdown(server) 38 | 39 | case s := <-signals: 40 | log.Println("signal received:", s) 41 | signal.Stop(signals) 42 | _ = shutdown(server) 43 | } 44 | } 45 | 46 | func listen(errs chan<- error, server *http.Server) { 47 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 48 | errs <- err 49 | } 50 | } 51 | 52 | func shutdown(server *http.Server) error { 53 | ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) 54 | defer cancel() 55 | 56 | server.SetKeepAlivesEnabled(false) 57 | return server.Shutdown(ctx) 58 | } 59 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/parse-token-for-security/jwt_private.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "bytes" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | ) 11 | 12 | const ( 13 | supportedTokenType = "JWT" 14 | supportedAlgo = "HS256" 15 | ) 16 | 17 | var byteDot = []byte(".") 18 | 19 | func parseHeader(data []byte) (Header, error) { 20 | d := json.NewDecoder( 21 | base64.NewDecoder(base64.RawURLEncoding, bytes.NewReader(data)), 22 | ) 23 | 24 | var header Header 25 | return header, d.Decode(&header) 26 | } 27 | 28 | func verifySignature(algo string, unsignedData, receivedSignature, secret []byte) error { 29 | if algo != supportedAlgo { 30 | return fmt.Errorf("%w: %q", ErrUnsupportedSigningAlgo, algo) 31 | } 32 | 33 | h := hmac.New(sha256.New, secret) 34 | h.Write(unsignedData) 35 | 36 | enc := base64.RawURLEncoding 37 | sign := make([]byte, enc.EncodedLen(h.Size())) 38 | enc.Encode(sign, h.Sum(nil)) 39 | 40 | if !hmac.Equal(sign, receivedSignature) { 41 | return fmt.Errorf("%w: %q vs expected %q", ErrInvalidSignature, receivedSignature, sign) 42 | } 43 | 44 | return nil 45 | } 46 | 47 | func parsePayload(data []byte) (Token, error) { 48 | d := json.NewDecoder( 49 | base64.NewDecoder(base64.RawURLEncoding, bytes.NewReader(data)), 50 | ) 51 | 52 | var token Token 53 | return token, d.Decode(&token) 54 | } 55 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/errors-is-via-errors-as/pipeline_err_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "math/rand" 8 | "net" 9 | "os" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestIsPipelineError(t *testing.T) { 16 | users := []string{"Bob", "John"} 17 | operations := []string{"bitcoin calculation", "file downloading"} 18 | steps := []string{"init", "hash", "download", "save"} 19 | 20 | for _, u1 := range users { 21 | for _, u2 := range users { 22 | for _, op1 := range operations { 23 | for _, op2 := range operations { 24 | err := fmt.Errorf("wrap: %w", &PipelineError{ 25 | User: u1, 26 | Name: op1, 27 | FailedSteps: steps[:rand.Intn(len(steps))], 28 | }) 29 | 30 | isEqual := (u1 == u2) && (op1 == op2) 31 | assert.Equal(t, isEqual, IsPipelineError(err, u2, op2), 32 | "err: %#v, user: %v, pipeline name: %v", err, u2, op2) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | func TestIsPipelineError_DifferentTypes(t *testing.T) { 40 | for i, err := range []error{ 41 | io.EOF, 42 | &os.PathError{Op: "parse", Path: "/tmp/file.txt"}, 43 | nil, 44 | net.UnknownNetworkError("tdp"), 45 | errors.New("integration error"), 46 | } { 47 | t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { 48 | assert.False(t, IsPipelineError(err, "parse", "/tmp/file.txt")) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/file-load-err-wrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | type FileLoadError struct { 10 | URL string 11 | Err error // Для хранения "родительской" ошибки. 12 | } 13 | 14 | func (p *FileLoadError) Error() string { 15 | // Текст "родительской ошибки" фигурирует в тексте этой ошибки. 16 | return fmt.Sprintf("%q: %v", p.URL, p.Err) 17 | } 18 | 19 | type File struct{} 20 | 21 | func getFile(u string) (File, error) { 22 | return File{}, context.Canceled 23 | } 24 | 25 | func loadFiles(urls ...string) ([]File, error) { 26 | files := make([]File, len(urls)) 27 | for i, url := range urls { 28 | f, err := getFile(url) 29 | if err != nil { 30 | return nil, &FileLoadError{url, err} // <- Врапим ошибку загрузки в *FileLoadError. 31 | } 32 | files[i] = f 33 | } 34 | return files, nil 35 | } 36 | 37 | func transfer() error { 38 | _, err := loadFiles("www.golang-ninja.ru") 39 | if err != nil { 40 | return fmt.Errorf("cannot load files: %w", err) 41 | } 42 | 43 | // ... 44 | return nil 45 | } 46 | 47 | func handle() error { 48 | if err := transfer(); err != nil { 49 | return fmt.Errorf("cannot transfer files: %w", err) 50 | } 51 | 52 | // ... 53 | return nil 54 | } 55 | 56 | func main() { 57 | var fileLoadErr *FileLoadError 58 | if err := handle(); errors.As(err, &fileLoadErr) { 59 | fmt.Println(fileLoadErr.URL) // www.golang-ninja.ru/ 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/parse-token-for-security/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // Исправь ParseToken так, чтобы из ошибки можно было достать email пользователя. 10 | 11 | // ParseToken парсит и валидирует токен jwt, проверяя, что он подписан 12 | // алгоритмом HMAC SHA256 с использованием ключа secret. 13 | func ParseToken(jwt, secret []byte) (Token, error) { 14 | if len(jwt) == 0 { 15 | return Token{}, ErrEmptyJWT 16 | } 17 | 18 | parts := bytes.Split(jwt, byteDot) 19 | if len(parts) != 3 { 20 | return Token{}, ErrInvalidTokenFormat 21 | } 22 | 23 | headerData, payloadData, signData := parts[0], parts[1], parts[2] 24 | 25 | h, err := parseHeader(headerData) 26 | if err != nil { 27 | return Token{}, fmt.Errorf("%w: %v", ErrInvalidHeaderEncoding, err) 28 | } 29 | 30 | if h.Typ != supportedTokenType { 31 | return Token{}, fmt.Errorf("%w: %q", ErrUnsupportedTokenType, h.Typ) 32 | } 33 | 34 | if err := verifySignature( 35 | h.Alg, 36 | bytes.Join([][]byte{parts[0], parts[1]}, byteDot), 37 | signData, 38 | secret, 39 | ); err != nil { 40 | return Token{}, fmt.Errorf("verify signature: %w", err) 41 | } 42 | 43 | t, err := parsePayload(payloadData) 44 | if err != nil { 45 | return Token{}, fmt.Errorf("%w: %v", ErrInvalidPayloadEncoding, err) 46 | } 47 | 48 | if time.Unix(t.ExpiredAt, 0).Before(time.Now()) { 49 | return Token{}, ErrExpiredToken 50 | } 51 | 52 | return t, nil 53 | } 54 | -------------------------------------------------------------------------------- /tasks/06-working-with-errors-in-tests/network-err-mock/fetcher.go: -------------------------------------------------------------------------------- 1 | package fetcher 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | var ( 12 | ErrFetchTimeout = errors.New("fetch url timeout") 13 | ErrFetchTmp = errors.New("fetch url temporary error") 14 | ) 15 | 16 | type Client interface { 17 | Do(req *http.Request) (*http.Response, error) 18 | } 19 | 20 | func FetchURL(ctx context.Context, client Client, url string) ([]byte, error) { 21 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) 22 | if err != nil { 23 | return nil, fmt.Errorf("create request: %v", err) 24 | } 25 | 26 | resp, err := client.Do(req) 27 | if err != nil { 28 | if isTimeout(err) { 29 | return nil, fmt.Errorf("%w: %v", ErrFetchTimeout, err) 30 | } 31 | if isTemporary(err) { 32 | return nil, fmt.Errorf("%w: %v", ErrFetchTmp, err) 33 | } 34 | return nil, fmt.Errorf("do request: %w", err) 35 | } 36 | defer resp.Body.Close() 37 | 38 | data, err := ioutil.ReadAll(resp.Body) 39 | if err != nil { 40 | return nil, fmt.Errorf("read all body: %v", err) 41 | } 42 | 43 | if err := resp.Body.Close(); err != nil { 44 | return nil, fmt.Errorf("close body: %v", err) 45 | } 46 | return data, nil 47 | } 48 | 49 | func isTimeout(err error) bool { 50 | var i interface{ Timeout() bool } 51 | return errors.As(err, &i) && i.Timeout() 52 | } 53 | 54 | func isTemporary(err error) bool { 55 | var i interface{ Temporary() bool } 56 | return errors.As(err, &i) && i.Temporary() 57 | } 58 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/command-executor/transport.go: -------------------------------------------------------------------------------- 1 | package commandexecutor 2 | 3 | import "context" 4 | 5 | //go:generate mockgen -source=$GOFILE -destination=mocks/transport_generated.go -package commandexecutormocks ITransport 6 | 7 | type ProtoCommand struct { 8 | ID string 9 | } 10 | 11 | type ProtoCommandStatus int 12 | 13 | const ( 14 | ProtoCommandStatusUnknownError ProtoCommandStatus = iota 15 | ProtoCommandStatusTimeoutError 16 | ProtoCommandStatusUnsupportedCommandError 17 | ProtoCommandStatusSuccess 18 | ) 19 | 20 | type ProtoCommandResult struct { 21 | ID string 22 | Status ProtoCommandStatus 23 | } 24 | 25 | type ITransport interface { 26 | // Context возвращает контекст текущего соединения. 27 | Context() context.Context 28 | 29 | // Recv позволяет принять команду. При успешном отсоединении клиента вернёт ошибку io.EOF. 30 | // Любая другая ошибка будет постоянной и при её появлении нужно завершать обработчик 31 | // транспорта в принципе. Вызов Recv блокирующий. 32 | // 33 | // Безопасно иметь одну горутину, вызывающую Recv и другую – вызывающую Send. 34 | // Но опасно вызывать Recv в разных горутинах. 35 | Recv() (*ProtoCommand, error) 36 | 37 | // Send позволяет отправить результат команды. Любая ошибка будет постоянной и 38 | // аналогичной той, что придёт в очередном Recv. Вызов Send блокирующий. 39 | // 40 | // Безопасно иметь одну горутину, вызывающую Recv и другую – вызывающую Send. 41 | // Но опасно вызывать Send в разных горутинах. 42 | Send(c *ProtoCommandResult) error 43 | } 44 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/byte-buffer-with-errors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const bufferMaxSize = 1024 6 | 7 | type MaxSizeExceededError struct { 8 | desiredLen int 9 | } 10 | 11 | func (e *MaxSizeExceededError) Error() string { 12 | return fmt.Sprintf("buffer max size exceeded: %d > %d", e.desiredLen, bufferMaxSize) 13 | } 14 | 15 | type EndOfBufferError struct{} 16 | 17 | func (b *EndOfBufferError) Error() string { 18 | return "end of buffer" 19 | } 20 | 21 | type ByteBuffer struct { 22 | // buffer представляет собой непосредственно буфер: содержит какие-то данные. 23 | buffer []byte 24 | // offset представляет собой смещение, указывающее на первый непрочитанный байт. 25 | offset int 26 | } 27 | 28 | func (b *ByteBuffer) Write(p []byte) (int, error) { 29 | if len(b.buffer)+len(p) > bufferMaxSize { 30 | return 0, &MaxSizeExceededError{desiredLen: len(b.buffer) + len(p)} 31 | } 32 | 33 | b.buffer = append(b.buffer, p...) 34 | return len(p), nil 35 | } 36 | 37 | func (b *ByteBuffer) Read(p []byte) (int, error) { 38 | if b.offset >= len(b.buffer) { 39 | return 0, new(EndOfBufferError) 40 | } 41 | 42 | n := copy(p, b.buffer[b.offset:]) 43 | b.offset += n 44 | return n, nil 45 | } 46 | 47 | func main() { 48 | var b ByteBuffer 49 | if _, err := b.Write([]byte("hello hello hello")); err != nil { 50 | panic(err) 51 | } 52 | 53 | p := make([]byte, 3) 54 | for { 55 | n, err := b.Read(p) 56 | if _, ok := err.(*EndOfBufferError); ok { 57 | break 58 | } 59 | fmt.Print(string(p[:n])) // hello hello hello 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/with-time-error/with_time_error_test.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func ExampleWithTimeError() { 14 | err := NewWithTimeError(context.Canceled) 15 | fmt.Println(err) 16 | // context canceled, occurred at: 2021-06-07T08:15:48.518835+03:00 17 | } 18 | 19 | func TestWithTimeError(t *testing.T) { 20 | var s int64 21 | timeNowMock := func() time.Time { 22 | s++ 23 | return time.Unix(s, 0) 24 | } 25 | 26 | newWithTimeErrorMock := func(err error) error { 27 | return newWithTimeError(err, timeNowMock) 28 | } 29 | 30 | var timed interface { 31 | Time() time.Time 32 | } 33 | 34 | err := newWithTimeErrorMock(context.Canceled) 35 | require.ErrorIs(t, err, context.Canceled) 36 | require.ErrorAs(t, err, &timed) 37 | require.Equal(t, 1, timed.Time().Second()) 38 | 39 | err = fmt.Errorf("cannot read file: %w", newWithTimeErrorMock(io.ErrUnexpectedEOF)) 40 | require.ErrorIs(t, err, io.ErrUnexpectedEOF) 41 | require.ErrorAs(t, err, &timed) 42 | require.Equal(t, 2, timed.Time().Second()) 43 | } 44 | 45 | func TestWithTimeError_FrozenTime(t *testing.T) { 46 | var timed interface { 47 | Time() time.Time 48 | } 49 | 50 | err := NewWithTimeError(context.Canceled) 51 | require.ErrorAs(t, err, &timed) 52 | 53 | t1 := timed.Time() 54 | time.Sleep(time.Millisecond) 55 | t2 := timed.Time() 56 | require.True(t, t1.Equal(t2), 57 | "Time() must return the time the error was created, not the time the method was called") 58 | } 59 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/pkg-wrap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type FileLoadError struct { 11 | URL string 12 | Err error // Для хранения "родительской" ошибки. 13 | } 14 | 15 | func (p *FileLoadError) Error() string { 16 | // Текст "родительской ошибки" фигурирует в тексте этой ошибки. 17 | return fmt.Sprintf("%q: %v", p.URL, p.Err) 18 | } 19 | 20 | type File struct{} 21 | 22 | func getFile(u string) (File, error) { 23 | return File{}, context.Canceled 24 | } 25 | 26 | func loadFiles(urls ...string) ([]File, error) { 27 | files := make([]File, len(urls)) 28 | for i, url := range urls { 29 | f, err := getFile(url) 30 | if err != nil { 31 | return nil, errors.WithStack(&FileLoadError{url, err}) 32 | } 33 | files[i] = f 34 | } 35 | return files, nil 36 | } 37 | 38 | func transfer() error { 39 | _, err := loadFiles("www.golang-ninja.ru") 40 | if err != nil { 41 | return errors.WithMessage(err, "cannot load files") 42 | } 43 | 44 | // ... 45 | return nil 46 | } 47 | 48 | func handle() error { 49 | if err := transfer(); err != nil { 50 | return errors.WithMessage(err, "cannot transfer files") 51 | } 52 | 53 | // ... 54 | return nil 55 | } 56 | 57 | func main() { 58 | err := handle() 59 | fmt.Printf("%+v\n\n", err) 60 | 61 | if f, ok := errors.Cause(err).(*FileLoadError); ok { 62 | fmt.Println(f.URL) 63 | } 64 | 65 | var fileLoadErr *FileLoadError 66 | if err := handle(); errors.As(err, &fileLoadErr) { 67 | fmt.Println(fileLoadErr.URL) // www.golang-ninja.ru/ 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/03-go-errors-concept/no-wrap-pain/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | var ( 11 | ErrExecSQL = errors.New("exec sql error") 12 | ErrInitTransaction = errors.New("init transaction error") 13 | ) 14 | 15 | type Entity struct { 16 | ID string 17 | } 18 | 19 | func errOccurred() bool { 20 | return rand.Float64() < 0.5 21 | } 22 | 23 | func getEntity() (Entity, error) { 24 | if errOccurred() { 25 | return Entity{}, ErrExecSQL 26 | } 27 | return Entity{ID: "some-id"}, nil 28 | } 29 | 30 | func updateEntity(_ Entity) error { 31 | if errOccurred() { 32 | return ErrExecSQL 33 | } 34 | return nil 35 | } 36 | 37 | func runInTransaction(f func() error) error { 38 | if errOccurred() { 39 | return ErrInitTransaction 40 | } 41 | return f() 42 | } 43 | 44 | func handler() error { 45 | var e Entity 46 | 47 | if err := runInTransaction(func() (opErr error) { 48 | e, opErr = getEntity() 49 | return opErr 50 | }); err != nil { 51 | return err 52 | } 53 | 54 | if err := runInTransaction(func() error { 55 | return updateEntity(e) 56 | }); err != nil { 57 | return err 58 | } 59 | 60 | return runInTransaction(func() (opErr error) { 61 | return updateEntity(e) 62 | }) 63 | } 64 | 65 | func init() { 66 | rand.Seed(time.Now().Unix()) 67 | } 68 | 69 | func main() { 70 | for i := 0; i < 5; i++ { 71 | fmt.Println(handler()) 72 | } 73 | /* 74 | От какой операции именно пришла ошибка? 75 | exec sql error 76 | init transaction error 77 | exec sql error 78 | exec sql error 79 | 80 | */ 81 | } 82 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/handling-sentinel-errors/handler_test.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestErrors(t *testing.T) { 13 | assert.EqualError(t, ErrAlreadyDone, "job is already done") 14 | assert.EqualError(t, ErrInconsistentData, "job payload is corrupted") 15 | assert.EqualError(t, ErrInvalidID, "invalid job id") 16 | assert.EqualError(t, ErrNotFound, "job wasn't found") 17 | assert.EqualError(t, ErrNotReady, "job is not ready to be performed") 18 | } 19 | 20 | func TestHandler_Handle(t *testing.T) { 21 | cases := []struct { 22 | job Job 23 | expectedErr error 24 | expectedPostpone time.Duration 25 | }{ 26 | { 27 | job: Job{ID: 0}, 28 | expectedErr: nil, 29 | }, 30 | { 31 | job: Job{ID: 1}, 32 | expectedErr: nil, 33 | }, 34 | { 35 | job: Job{ID: 2}, 36 | expectedErr: nil, 37 | expectedPostpone: time.Second, 38 | }, 39 | { 40 | job: Job{ID: 3}, 41 | expectedErr: nil, 42 | }, 43 | { 44 | job: Job{ID: 4}, 45 | expectedErr: nil, 46 | }, 47 | { 48 | job: Job{ID: 5}, 49 | expectedErr: nil, 50 | }, 51 | { 52 | job: Job{ID: 6}, 53 | expectedErr: io.EOF, 54 | }, 55 | } 56 | 57 | for _, tt := range cases { 58 | t.Run(fmt.Sprintf("handle job #%d", tt.job.ID), func(t *testing.T) { 59 | var h Handler 60 | p, err := h.Handle(tt.job) 61 | assert.Equal(t, tt.expectedErr, err) 62 | assert.Equal(t, tt.expectedPostpone, p) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/extract/extract_test.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestExtract(t *testing.T) { 14 | errInvalidCursor := errors.New("invalid cursor") 15 | vWrapped := fmt.Errorf("parse request: %v", errInvalidCursor) 16 | 17 | cases := []struct { 18 | in error 19 | expected []error 20 | }{ 21 | { 22 | in: errInvalidCursor, 23 | expected: []error{errInvalidCursor}, 24 | }, 25 | { 26 | in: vWrapped, 27 | expected: []error{vWrapped}, 28 | }, 29 | { 30 | in: fmt.Errorf("parse request: %w", errInvalidCursor), 31 | expected: []error{errInvalidCursor}, 32 | }, 33 | { 34 | in: fmt.Errorf("parse request: %w", fmt.Errorf("decode cursor: %w", errInvalidCursor)), 35 | expected: []error{errInvalidCursor}, 36 | }, 37 | { 38 | in: fmt.Errorf("parse request: %w: %w", errInvalidCursor, io.EOF), 39 | expected: []error{errInvalidCursor, io.EOF}, 40 | }, 41 | { 42 | in: fmt.Errorf("parse request: %w: %w: %w: %w", 43 | context.Canceled, 44 | fmt.Errorf("%w", errInvalidCursor), 45 | fmt.Errorf("another yet error: %w: %w", io.EOF, context.DeadlineExceeded), 46 | nil, 47 | ), 48 | expected: []error{ 49 | context.Canceled, 50 | errInvalidCursor, 51 | io.EOF, 52 | context.DeadlineExceeded, 53 | }, 54 | }, 55 | } 56 | 57 | for _, tt := range cases { 58 | t.Run("", func(t *testing.T) { 59 | chain := Extract(tt.in) 60 | assert.Equal(t, tt.expected, chain) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/07-working-with-errors-in-concurrency/errgroup-with-context-diff-parent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | 12 | "golang.org/x/sync/errgroup" 13 | ) 14 | 15 | const ( 16 | workerInterval = 3 * time.Second 17 | workersCount = 10 18 | ) 19 | 20 | func init() { 21 | rand.Seed(time.Now().Unix()) 22 | } 23 | 24 | func main() { 25 | ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 26 | defer cancel() 27 | 28 | // eg, ctx := errgroup.WithContext(ctx) 29 | // Попробуйте раскомментировать строчку ниже и закомментировать строчку выше, 30 | // и посмотреть, как поменяется поведение программы. 31 | var eg errgroup.Group 32 | 33 | for i := 0; i < workersCount; i++ { 34 | i := i 35 | eg.Go(func() error { 36 | t := time.NewTicker(workerInterval) 37 | defer t.Stop() 38 | 39 | for { 40 | select { 41 | case <-ctx.Done(): 42 | return nil 43 | case <-t.C: 44 | if err := task(ctx, i); err != nil { 45 | return fmt.Errorf("do task: %v", err) 46 | } 47 | } 48 | } 49 | }) 50 | } 51 | 52 | if err := eg.Wait(); err != nil { 53 | fmt.Println(err) 54 | } 55 | } 56 | 57 | func task(ctx context.Context, taskID int) error { 58 | fmt.Println(taskID, ": do useful work...") 59 | 60 | if rand.Float64() <= 0.3 { // Возвращаем случайную ошибку в 30% случаях. 61 | return errors.New("unknown error") 62 | } 63 | 64 | select { 65 | case <-ctx.Done(): 66 | return ctx.Err() 67 | case <-time.After(5 * time.Second): 68 | return nil 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/04-non-standard-modules/uber-multierr-append-invoke/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "go.uber.org/multierr" 11 | ) 12 | 13 | func main() { 14 | if err := processFile("/etc/hosts"); err != nil { // false 15 | fmt.Printf("%v\n", err) 16 | } 17 | 18 | if err := processCloserMock(); err != nil { 19 | fmt.Printf("%v\n", err) // close error 20 | } 21 | 22 | if err := processCloserMockWithError(); err != nil { 23 | fmt.Printf("%v\n", err) // process error; close error 24 | } 25 | } 26 | 27 | // processFile – пример штатного использования multierr.AppendInvoke. 28 | // Обратите внимание, что multierr.AppendInvoke используется вместе с 29 | // именованным возвращаемым аргументом err. 30 | func processFile(path string) (err error) { 31 | f, err := os.Open(path) 32 | if err != nil { 33 | return fmt.Errorf("open file: %v", err) 34 | } 35 | defer multierr.AppendInvoke(&err, multierr.Close(f)) 36 | 37 | scanner := bufio.NewScanner(f) 38 | defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err)) 39 | 40 | for scanner.Scan() { 41 | fmt.Println(scanner.Text()) 42 | } 43 | return nil 44 | } 45 | 46 | var _ io.Closer = CloserMock{} 47 | 48 | type CloserMock struct{} 49 | 50 | func (c CloserMock) Close() error { 51 | return errors.New("close error") 52 | } 53 | 54 | func processCloserMock() (err error) { 55 | defer multierr.AppendInvoke(&err, multierr.Close(CloserMock{})) 56 | return nil 57 | } 58 | 59 | func processCloserMockWithError() (err error) { 60 | defer multierr.AppendInvoke(&err, multierr.Close(CloserMock{})) 61 | return errors.New("process error") 62 | } 63 | -------------------------------------------------------------------------------- /tasks/03-go-errors-concept/fix-opaque-errors/is_temporary_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestIsTemporary(t *testing.T) { 12 | cases := []struct { 13 | err error 14 | isTemporary bool 15 | }{ 16 | { 17 | err: errors.New("integrity error"), 18 | isTemporary: false, 19 | }, 20 | { 21 | err: newNetErrorMock(true), 22 | isTemporary: true, 23 | }, 24 | { 25 | err: fmt.Errorf("wrap 1: %w", newNetErrorMock(true)), 26 | isTemporary: true, 27 | }, 28 | { 29 | err: fmt.Errorf("wrap 2: %w", fmt.Errorf("wrap 1: %w", newNetErrorMock(true))), 30 | isTemporary: true, 31 | }, 32 | { 33 | err: newNetErrorMock(false), 34 | isTemporary: false, 35 | }, 36 | { 37 | err: fmt.Errorf("wrap 1: %w", newNetErrorMock(false)), 38 | isTemporary: false, 39 | }, 40 | { 41 | err: fmt.Errorf("wrap 2: %w", fmt.Errorf("wrap 1: %w", newNetErrorMock(false))), 42 | isTemporary: false, 43 | }, 44 | } 45 | 46 | for i, tt := range cases { 47 | t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { 48 | assert.Equal(t, tt.isTemporary, IsTemporary(tt.err)) 49 | }) 50 | } 51 | } 52 | 53 | type netErrorMock struct { //nolint:errname 54 | isTmp bool 55 | } 56 | 57 | func newNetErrorMock(isTmp bool) *netErrorMock { 58 | return &netErrorMock{isTmp: isTmp} 59 | } 60 | 61 | func (n *netErrorMock) Error() string { 62 | return fmt.Sprintf("network error (temporary: %t)", n.isTmp) 63 | } 64 | 65 | func (n *netErrorMock) IsTemporary() bool { 66 | return n.isTmp 67 | } 68 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/error-context/context_test.go: -------------------------------------------------------------------------------- 1 | package errctx_test 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | errctx "github.com/golang-ninja-courses/error-handling-mastery/tasks/05-errors-best-practices/error-context" 11 | ) 12 | 13 | func TestAppendToNilIsNil(t *testing.T) { 14 | err := errctx.AppendTo(nil, errctx.Fields{"uid": "Stepan"}) 15 | assert.Nil(t, err) 16 | } 17 | 18 | func TestNoErrorNoContext(t *testing.T) { 19 | ctx := errctx.From(nil) 20 | assert.Empty(t, ctx) 21 | } 22 | 23 | func TestWrapping(t *testing.T) { 24 | err := fmt.Errorf("read file: %w", io.EOF) 25 | err = errctx.AppendTo(err, errctx.Fields{"key1": "value1", "key2": "value2"}) 26 | 27 | err = fmt.Errorf("do foo: %w", err) 28 | err = errctx.AppendTo(err, errctx.Fields{"key1": 11, "key3": 3}) 29 | 30 | err = fmt.Errorf("do bar: %w", err) 31 | err = errctx.AppendTo(err, errctx.Fields{"key4": "value4"}) 32 | 33 | assert.ErrorIs(t, err, io.EOF) 34 | assert.EqualError(t, err, "do bar: do foo: read file: EOF") 35 | 36 | ctx := errctx.From(err) 37 | assert.Equal(t, errctx.Fields{ 38 | "key1": "value1", 39 | "key2": "value2", 40 | "key3": 3, 41 | "key4": "value4", 42 | }, ctx) 43 | } 44 | 45 | func TestImmutableCtx(t *testing.T) { 46 | ctx := errctx.Fields{"key1": "value1", "key2": "value2"} 47 | err := errctx.AppendTo(io.EOF, ctx) 48 | extractedCtx := errctx.From(err) 49 | ctx["new_key"] = "new_value" 50 | assert.NotEqual(t, ctx, extractedCtx) 51 | 52 | extractedCtx["new_key"] = "new_value" 53 | newExtractedCtx := errctx.From(err) 54 | assert.NotEqual(t, extractedCtx, newExtractedCtx) 55 | } 56 | -------------------------------------------------------------------------------- /tasks/07-working-with-errors-in-concurrency/errgroup-task-runner/task_runner.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | var ( 12 | ErrInvalidWorkersCount = errors.New("invalid workers count") 13 | ErrFatal = errors.New("fatal error") 14 | ) 15 | 16 | type Task interface { 17 | // Handle выполняет задачу. 18 | Handle(ctx context.Context) error 19 | 20 | // ExecutionTimeout возвращает промежуток времени, 21 | // в течение которого задача должна быть выполнена. 22 | ExecutionTimeout() time.Duration 23 | } 24 | 25 | // Run выполняет задачи из очереди tasks с некоторыми условиями: 26 | // – параллельно обрабатываются workersCount задач; 27 | // - если workersCount <= 0, то функция возвращает ErrInvalidWorkersCount; 28 | // – для обработки задачи Task вызывается функция process; 29 | // - если во время работы process возникла ошибка ErrFatal, то обработка очереди 30 | // завершается с возвратом этой ошибки; 31 | // - при любой другой ошибке обработка очереди продолжается. 32 | func Run(ctx context.Context, workersCount int, tasks <-chan Task) error { 33 | // Для сохранения импортов. Удали эти строки. 34 | _ = errors.Is 35 | _ = errgroup.Group{} 36 | 37 | // Реализуй меня. 38 | return nil 39 | } 40 | 41 | // process выполняет задачу task с некоторыми условиями: 42 | // – задача task выполняется не дольше Task.ExecutionTimeout(); 43 | // – если во время выполнения задачи возникает ошибка, то она возвращается наружу; 44 | // – при возникновении паники функция возвращает ErrFatal. 45 | func process(ctx context.Context, task Task) (err error) { 46 | // Реализуй меня. 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/pretty-error/pretty_test.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestPretty(t *testing.T) { 13 | firstErr := sql.ErrNoRows 14 | 15 | err := fmt.Errorf("cannot get user schedule: %w", 16 | fmt.Errorf("cannot build data for event: %w", 17 | fmt.Errorf("cannot get image: %w", 18 | fmt.Errorf("cannot get image: %w", 19 | fmt.Errorf("cannot get image: %w", 20 | fmt.Errorf("cannot get image for event: %w", firstErr)))))) 21 | t.Log(err) 22 | 23 | pErr := Pretty(err) 24 | require.Error(t, pErr) 25 | t.Log(pErr) 26 | 27 | assert.ErrorIs(t, pErr, firstErr) 28 | assert.Equal(t, `cannot get user schedule: 29 | cannot build data for event: 30 | cannot get image: 31 | cannot get image: 32 | cannot get image: 33 | cannot get image for event: 34 | `+firstErr.Error(), pErr.Error()) 35 | } 36 | 37 | func TestPretty_MoreColons(t *testing.T) { 38 | firstErr := sql.ErrNoRows 39 | 40 | err := fmt.Errorf("user service: cannot get user schedule: %w", 41 | fmt.Errorf("event service: cannot build data for event: %w", 42 | fmt.Errorf("storage: cannot get image: %w", firstErr))) 43 | t.Log(err) 44 | 45 | pErr := Pretty(err) 46 | require.Error(t, pErr) 47 | t.Log(pErr) 48 | 49 | assert.ErrorIs(t, pErr, firstErr) 50 | assert.Equal(t, `user service: cannot get user schedule: 51 | event service: cannot build data for event: 52 | storage: cannot get image: 53 | `+firstErr.Error(), pErr.Error()) 54 | } 55 | 56 | func TestPretty_NoError(t *testing.T) { 57 | err := Pretty(nil) 58 | require.NoError(t, err) 59 | } 60 | -------------------------------------------------------------------------------- /examples/06-working-with-errors-in-tests/testify-require-and-assert/example_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type User struct { 13 | ID string 14 | Email string 15 | RegisteredAt time.Time 16 | } 17 | 18 | func TestAssertOnly_1(t *testing.T) { 19 | getUser := func() (*User, error) { 20 | return nil, errors.New("user not found") 21 | } 22 | 23 | u, err := getUser() 24 | assert.NoError(t, err) 25 | assert.Equal(t, "user-id", u.ID) // Получим панику "... nil pointer dereference". 26 | assert.Equal(t, "user-email", u.Email) 27 | } 28 | 29 | func TestAssertOnly_2(t *testing.T) { 30 | getUsers := func() ([]User, error) { 31 | return nil, nil 32 | } 33 | 34 | users, err := getUsers() 35 | assert.NoError(t, err) 36 | assert.Len(t, users, 3) 37 | 38 | u := users[0] // Получим панику "... index out of range [0] ...". 39 | assert.Equal(t, "user-id", u.ID) 40 | assert.Equal(t, "user-email", u.Email) 41 | } 42 | 43 | func TestRequireAndAssert(t *testing.T) { 44 | getUser := func() (*User, error) { 45 | return new(User), nil 46 | } 47 | 48 | u, err := getUser() 49 | require.NoError(t, err) 50 | assert.Equal(t, "user-id", u.ID) 51 | assert.Equal(t, "user-email", u.Email) 52 | } 53 | 54 | func TestRequireOnly(t *testing.T) { 55 | getUser := func() (*User, error) { 56 | return new(User), nil 57 | } 58 | 59 | u, err := getUser() 60 | require.NoError(t, err) 61 | require.Equal(t, "user-id", u.ID) // Выйдем, например, уже здесь, хотя удобнее увидеть сразу все ошибки. 62 | require.Equal(t, "user-email", u.Email) 63 | } 64 | -------------------------------------------------------------------------------- /tasks/05-errors-best-practices/template-err/tmpl_test.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "unsafe" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestErrorsAreFilled(t *testing.T) { 13 | assert.NotNil(t, errParseTemplate) 14 | assert.NotNil(t, errExecuteTemplate) 15 | } 16 | 17 | func TestParseAndExecuteTemplate_Success(t *testing.T) { 18 | tmpl := ` 19 | 20 | 21 | {{ with .Name -}} 22 |

Hello {{ . }}!

23 | {{- end }} 24 | 25 | ` 26 | b := bytes.NewBuffer(nil) 27 | 28 | err := ParseAndExecuteTemplate(b, "greeting", tmpl, struct{ Name string }{Name: "Anthony"}) 29 | require.NoError(t, err) 30 | assert.Equal(t, ` 31 | 32 | 33 |

Hello Anthony!

34 | 35 | `, b.String()) 36 | } 37 | 38 | func TestParseAndExecuteTemplate_InvalidTemplate(t *testing.T) { 39 | tmpl := ` 40 | 41 | 42 | {{{ with .Name -}} 43 |

Hello {{ . }}!

44 | {{- end }} 45 | 46 | ` 47 | b := bytes.NewBuffer(nil) 48 | 49 | err := ParseAndExecuteTemplate(b, "greeting", tmpl, struct{ Name string }{Name: "Anthony"}) 50 | require.Error(t, err) 51 | assert.ErrorIs(t, err, errParseTemplate) 52 | } 53 | 54 | func TestParseAndExecuteTemplate_ExecutingError(t *testing.T) { 55 | tmpl := ` 56 | 57 | 58 | {{ with .Name -}} 59 |

Hello {{ . }}!

60 | {{- end }} 61 | 62 | ` 63 | b := bytes.NewBuffer(nil) 64 | 65 | err := ParseAndExecuteTemplate(b, "greeting", tmpl, struct{ Name unsafe.Pointer }{}) 66 | require.Error(t, err) 67 | assert.ErrorIs(t, err, errExecuteTemplate) 68 | } 69 | --------------------------------------------------------------------------------