From 8322984cb88e2110ac05bfc69e02531def36d1e3 Mon Sep 17 00:00:00 2001 From: June <12543047+junedev@users.noreply.github.com> Date: Wed, 11 Oct 2023 22:04:21 +0200 Subject: [PATCH 1/2] Add hacktoberfest label --- .appends/.github/labels.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.appends/.github/labels.yml b/.appends/.github/labels.yml index fc6b8319e..54215053f 100644 --- a/.appends/.github/labels.yml +++ b/.appends/.github/labels.yml @@ -78,3 +78,7 @@ - name: "chore" description: "Some cleanup/house-keeping needs to be done here." color: "495747" + +- name: "hacktoberfest-accepted" + description: "" + color: "ffa500" From 9038aee4d897be0ac6ec90e0cab4bfecb7ccec92 Mon Sep 17 00:00:00 2001 From: junedev <12543047+junedev@users.noreply.github.com> Date: Thu, 23 Nov 2023 21:41:29 +0100 Subject: [PATCH 2/2] skeleton --- concepts/error-wrapping/introduction.md | 2 - exercises/concept/let-me-in/.docs/hints.md | 9 ++ .../concept/let-me-in/.docs/instructions.md | 20 +++ .../concept/let-me-in/.docs/introduction.md | 152 ++++++++++++++++++ exercises/concept/let-me-in/.meta/config.json | 24 +++ exercises/concept/let-me-in/.meta/design.md | 21 +++ exercises/concept/let-me-in/.meta/exemplar.go | 1 + exercises/concept/let-me-in/go.mod | 3 + exercises/concept/let-me-in/let_me_in.go | 23 +++ exercises/concept/let-me-in/let_me_in_test.go | 1 + 10 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 exercises/concept/let-me-in/.docs/hints.md create mode 100644 exercises/concept/let-me-in/.docs/instructions.md create mode 100644 exercises/concept/let-me-in/.docs/introduction.md create mode 100644 exercises/concept/let-me-in/.meta/config.json create mode 100644 exercises/concept/let-me-in/.meta/design.md create mode 100644 exercises/concept/let-me-in/.meta/exemplar.go create mode 100644 exercises/concept/let-me-in/go.mod create mode 100644 exercises/concept/let-me-in/let_me_in.go create mode 100644 exercises/concept/let-me-in/let_me_in_test.go diff --git a/concepts/error-wrapping/introduction.md b/concepts/error-wrapping/introduction.md index 741080e7e..7c0c25e61 100644 --- a/concepts/error-wrapping/introduction.md +++ b/concepts/error-wrapping/introduction.md @@ -150,5 +150,3 @@ func (e *SpecialError) Unwrap() error { [concept-errors]: /tracks/go/concepts/errors [concept-pointers]: /tracks/go/concepts/pointers [google-go-styleguide]: https://google.github.io/styleguide/go/best-practices#adding-information-to-errors -[release-notes]: https://tip.golang.org/doc/go1.20#errors -[doc-join]: https://pkg.go.dev/errors#Join \ No newline at end of file diff --git a/exercises/concept/let-me-in/.docs/hints.md b/exercises/concept/let-me-in/.docs/hints.md new file mode 100644 index 000000000..8a1d53ae2 --- /dev/null +++ b/exercises/concept/let-me-in/.docs/hints.md @@ -0,0 +1,9 @@ +# Hints + +## General + +- ... + +## 1. Create the abstract greeting functionality + +- ... \ No newline at end of file diff --git a/exercises/concept/let-me-in/.docs/instructions.md b/exercises/concept/let-me-in/.docs/instructions.md new file mode 100644 index 000000000..b83a3cf9b --- /dev/null +++ b/exercises/concept/let-me-in/.docs/instructions.md @@ -0,0 +1,20 @@ +# Instructions + +... + +## 1. ... + +... + +1. +Check for unexpected behavoir +if another error return it as is +if there was no error return true + +2. +extend to check for ErrorForbidden + +3. Version 2 +- errors.As variant + +1. type assertion \ No newline at end of file diff --git a/exercises/concept/let-me-in/.docs/introduction.md b/exercises/concept/let-me-in/.docs/introduction.md new file mode 100644 index 000000000..5548d6dde --- /dev/null +++ b/exercises/concept/let-me-in/.docs/introduction.md @@ -0,0 +1,152 @@ +# Introduction + +## Adding context to errors + +We explored basic error handling in Go in the [errors concept][concept-errors]. +As you learned there, by default errors do not carry around stack traces. +That makes it crucial to ensure the error itself contains enough information to identify the problem. + +If we wanted to add information to an existing error with the tools we already know, we could write something like this to create a new error with the combined text of the original error and the additional information: + +```go +err := errors.New(fmt.Sprintf("parsing age for user %d: %v", userID, originalError)) +``` + +Luckily, the `fmt` package from the standard library contains a short-hand version for this in form of the `Errorf` function. +That allows you, for example, to easily add information about the context in which the error occurred. + +```go +originalError := errors.New("unexpected negative number") +userID := 123 +err := fmt.Errorf("parsing age for user %d: %v", userID, originalError) +err.Error() +// => "parsing age for user 123: unexpected negative number" +``` + +Often this way of adding information for an error is good enough but there are cases where you want to allow the consumer of your error to check for or retrieve the original underlying error. +Adding context in way that allows this is called "wrapping an error" in Go. + +## Wrapping errors and checking for sentinel errors + +Error wrapping can be achieved with a very minor change to the code we saw above. +To wrap an error, you need to use the formatting verb `%w` instead of `%v`. +Behind the scenes, this will make sure that the resulting error implements an `Unwrap` method which returns the original error. +Because of that, then `errors.Is` can be used to check whether a specific sentinel error appears somewhere along the "error chain". +It does that by secretly calling `Unwrap` repeatedly until the error in question was found or the chain ended. + +```go +originalError := errors.New("unexpected negative number") +err := fmt.Errorf("parsing age: %w", originalError) +errors.Is(err, originalError) +// => true +``` + +It is good practice to use `%v` by default and only use `%w` if you explicitly want to allow your consumer to access an underlying error ([Google Go Styleguide][google-go-styleguide]). + +If you find ourself in a situation where you want to check for a sentinel error but explicitly don't want to unwrap, you can use `==` instead of `errors.Is`. + +```go +var originalError = errors.New("unexpected negative number") +func someFunc() error { + return originalError +} + +err := someFunc() +err == originalError +// => true +``` + +It is fine to work with `errors.Is` by default, but you should always be aware that this means the whole error chain will be searched. + +## Checking for (custom) error types + +There is an equivalent to `errors.Is` that allows to check for and retrieve an error of a specific type from the error chain. +The function for that is `errors.As` and just like `errors.Is` it will search for the given error type along the whole chain. +`errors.As` does only then extract the error it found that matches the type so you can further work with it, e.g. retrieve specific fields. + +```go +type MyCustomError struct { + Message string + Details string +} + +func (e *MyCustomError) Error() string { + return fmt.Sprintf("%s, details: %s", e.Message, e.Details) +} + +func someFunc() error { + originalError := &MyCustomError{ + Message: "some message", + Details: "some details", + } + + return fmt.Errorf("doing something: %w", originalError) +} + +err := someFunc() +var customError *MyCustomError +errors.As(err, &customError) +// => true + +// customError now contains the error that was found in the error chain. +customError.Details +// => "some details" +``` + +~~~~exercism/caution +Be careful with the syntax regarding the pointers above. +The code will only work and compile correctly if `customError` has the exact type that implements the error interface. +In our case, that is `*MyCustomError` (a [pointer][concept-pointers] to `MyCustomError`), not `MyCustomError` itself. + +On top of that, the second argument needs to be a pointer to the error variable. +Since our error is already a pointer, what we are passing to `errors.As` is a pointer to a pointer (to MyCustomError). +Only with this set up correctly, Go can then fill the variable with the error it found. +~~~~ + +As before, `errors.As` would not have found the error type if `%v` would have been used when calling `Errorf`. + +If you don't want to unwrap for some reason, type assertion can be used instead (equivalent to `==` above). + +```go +// MyCustomError defined as above. + +func someFunc() error { + return &MyCustomError{ + Message: "some message", + Details: "some details", + } +} + +err := someFunc() +customError, ok := err.(*CustomError) +// "ok" is now true +customError.Details +// => "some details" +``` + +Type assertion will not be able to identify the error type if the error would have been wrapped. + +## Allowing errors of custom types to be unwrapped + +Sometimes just wrapping an error with some additional text is not enough. +You can create a custom error type instead that holds the original error and the additional structured data that you want to add. +If you want to allow unwrapping for your error type, the only thing you have to do is to manually add an `Unwrap() error` method so the `Unwrap` interface is satisfied. + +```go +type SpecialError struct { + originalError error + metadata string +} + +func (e *SpecialError) Error() string { + // The usual serialization code goes here. +} + +func (e *SpecialError) Unwrap() error { + return e.originalError +} +``` + +[concept-errors]: /tracks/go/concepts/errors +[concept-pointers]: /tracks/go/concepts/pointers +[google-go-styleguide]: https://google.github.io/styleguide/go/best-practices#adding-information-to-errors \ No newline at end of file diff --git a/exercises/concept/let-me-in/.meta/config.json b/exercises/concept/let-me-in/.meta/config.json new file mode 100644 index 000000000..8bfcf7435 --- /dev/null +++ b/exercises/concept/let-me-in/.meta/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "junedev" + ], + "files": { + "solution": [ + "let_me_in.go" + ], + "test": [ + "let_me_in.go" + ], + "exemplar": [ + ".meta/exemplar.go" + ], + "invalidator": [ + "go.mod" + ] + }, + "icon": "developer-privileges", + "blurb": "Learn about adding context to errors and checking for certain errors/ error types by integrating a new authorization system called LET-ME-IN.", + "custom": { + "taskIdsEnabled": true + } +} diff --git a/exercises/concept/let-me-in/.meta/design.md b/exercises/concept/let-me-in/.meta/design.md new file mode 100644 index 000000000..c7d2f0590 --- /dev/null +++ b/exercises/concept/let-me-in/.meta/design.md @@ -0,0 +1,21 @@ +# Design + +## Learning objectives + +- ... + +## Out of Scope + +The following topics will be introduced later and should therefore not be part of this concept exercise. + +- ... + +## Concepts + +The Concepts this exercise unlocks are: + +- `error-wrapping` + +## Prerequisites + +- `errors` to ... diff --git a/exercises/concept/let-me-in/.meta/exemplar.go b/exercises/concept/let-me-in/.meta/exemplar.go new file mode 100644 index 000000000..0b552d8ea --- /dev/null +++ b/exercises/concept/let-me-in/.meta/exemplar.go @@ -0,0 +1 @@ +package letmein diff --git a/exercises/concept/let-me-in/go.mod b/exercises/concept/let-me-in/go.mod new file mode 100644 index 000000000..fa3066af4 --- /dev/null +++ b/exercises/concept/let-me-in/go.mod @@ -0,0 +1,3 @@ +module airportrobot + +go 1.18 diff --git a/exercises/concept/let-me-in/let_me_in.go b/exercises/concept/let-me-in/let_me_in.go new file mode 100644 index 000000000..ec6c02931 --- /dev/null +++ b/exercises/concept/let-me-in/let_me_in.go @@ -0,0 +1,23 @@ +package letmein + +import ( + "errors" + "fmt" +) + +func IsAuthorized(user, action, token) (bool, error) { + err := CallLetMeIn(user, action, token) + if errors.Is(err, ErrUnexpectedBehavior) { + return false, fmt.Errorf("calling LET-ME-IN: %v", err) + } else if err == ErrForbidden { + return false, errors.New("got forbidden from LET-ME-IN") + } else if err != nil { + return false, err + } + + return true, nil +} + +func IsAuthorizedV2() { + +} diff --git a/exercises/concept/let-me-in/let_me_in_test.go b/exercises/concept/let-me-in/let_me_in_test.go new file mode 100644 index 000000000..0b552d8ea --- /dev/null +++ b/exercises/concept/let-me-in/let_me_in_test.go @@ -0,0 +1 @@ +package letmein