AWS Step Function error handling for Go Lambda - go

I cannot find a detailed explanation of how to define the error condition matcher in the Step function, based on the error returned by the Go handler.
The handler is a bog-standard Go function, returns an error if it gets a 503 from an upstream service:
func HandleHotelBookingRequest(ctx context.Context, booking HotelBookingRequest) (
confirmation HotelBookingResponse, err error) {
...
if statusCode == http.StatusServiceUnavailable {
err = errors.New("TransientError")
} else {
I can control what the function returns, and how it formats the string; I cannot find any real information about what to use here (or in a Catch clause, for that matter), so tht this matches the above:
"Retry": [
{
"ErrorEquals": [
"TransientError"
],
"BackoffRate": 1,
"IntervalSeconds": 1,
"MaxAttempts": 3,
"Comment": "Retry for Transient Errors (503)"
}
]
When I test the Lambda in the Console, this is what I get (as expected) when the upstream returns a 503:
{
"errorMessage": "TransientError",
"errorType": "errorString"
}
And I have the distinct impression (but not quite sure how to validate this) that if I change to:
"ErrorEquals": [
"errorString"
],
the Retry works (at least, looking at the CloudWatch logs, I can see the transient errors being logged, but the Step function eventually succeeds).
I cannot find much documentation on this but:
would it be possible to match on the actual error message (I saw that the API Gateway allows to do that, using a RegEx);
if that's not possible, should I return a different "error type", instead of error
Thanks in advance!

Finally solved the riddle; in the end, it was trivial and fairly identical to the JavaScript approach (which (a) gave me the hint and (b) is widely documented in examples); however, as I was unable to find a Go-specific answer anywhere (in AWS -expansive, good, detailed- documentation, Google, here) I am posting it here for future reference.
TL;DR - define your own implementation of the error interface and return an object of that type, instead of the bog-standard fmt.Error(), then use the type name in the ErrorEquals clause.
A very basic example implementation is shown in this gist.
To test this, I have created an ErrorStateMachine (JSON definition in the same gist) and selected a different catcher based on the ErrorEquals type:
{
"ErrorEquals": [
"HandlerError"
],
"Next": "Handler Error"
}
Testing the Step Function with different Outcome inputs, causes different paths to be chosen.
What I guess tripped me off was that I am a relative beginner when it comes to Go and I hadn't realized that errorString is the actual type of the error interface returned by the errors.New() method, which is used inside fmt.Errorf():
// in errors/errors.go
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
I had naively assumed that this was just something that AWS named.
An interesting twist (which is not really ideal) is that the actual error message is "wrapped" in the Step function output and may be a bit cumbersome to parse in subsequent steps:
{
"Error": "HandlerError",
"Cause": "{\"errorMessage\":\"error from a failed handler\",\"errorType\":\"HandlerError\"}"
}
It would have certainly been a lot more developer-friendly to have the actual error message (generated by Error()) to be emitted straight into the Cause field.
Hope others find this useful and won't have to waste time on this like I did.

Related

Compare dynamic error message using Error.Is function

I have a dynamic error message for example
metadata.name: invalid value "test-value"
"test-value" will be dynamic, and I need to do something if this error pattern appears
How to check the errors pattern with Error.Is function?
You say your error has a dynamic message. I think you mean that you're defining a type that satisfies the error interface. (If you're using fmt.Errorf instead though, you should define a type for this use case).
type invalidValueError struct {
value string
}
func (e *invalidValidError) Error() string {
return fmt.Sprintf("invalid value %q", e.value)
}
You can check whether any given error has this type using errors.As. This will match not only the error itself, but any errors it's wrapping.
if ive := (*invalidValueError)(nil); errors.As(err, &ive) {
// got an *invalidValueError
}
Avoid matching the text of error messages if at all possible. They can change on different systems or in different locales, and they're generally not covered by compatibility promises.

how to decode a byte slice to different structs elegantly

var response Response
switch wrapper.Domain {
case "":
response = new(TypeA)
case "TypeB":
response = new(TypeB)
case "TypeC":
response = new(TypeC)
case "TypeD":
response = new(TypeD)
}
_ = decoder.Decode(response)
As shown in the code snippet, I got enough information from the Domain filed of wrapper to determine the type of response, and for each type, the following operations are performed:
create a new instance of that type using new
use the decoder to decode the byte slice to the instance created in step 1
I am wondering if there is a way to make the first step more generic and get rid of the switch statement.
A bit about your code
As per discussion in comments, I would like to share some experience.
I do not see nothing bad in your solution, but there are few options to improve it, depends what you want to do.
Your code looks like classic Factory. The Factory is a pattern, that create object of a single family, based on some input parameters.
In Golang this is commonly used in simpler way as a Factory Method, sometimes called Factory function.
Example:
type Vehicle interface {};
type Car struct {}
func NewCar() Vehicle {
return &Car{}
}
But you can easily expand it to do something like you:
package main
import (
"fmt"
"strings"
)
type Vehicle interface {}
type Car struct {}
type Bike struct {}
type Motorbike struct {}
// NewDrivingLicenseCar returns a car for a user, to perform
// the driving license exam.
func NewDrivingLicenseCar(drivingLicense string) (Vehicle, error) {
switch strings.ToLower(drivingLicense) {
case "car":
return &Car{}, nil
case "motorbike":
return &Motorbike{}, nil
case "bike":
return &Bike{}, nil
default:
return nil, fmt.Errorf("Sorry, We are not allowed to make exam for your type of car: \"%s\"", drivingLicense)
}
}
func main() {
fmt.Println(NewDrivingLicenseCar("Car"))
fmt.Println(NewDrivingLicenseCar("Tank"))
}
Above code produces output:
&{} <nil>
<nil> Sorry, We are not allowed to make exam for your type of car: "Tank"
So probably you can improve your code by:
Closing into a single function, that takes a string and produces the Response object
Adding some validation and the error handling
Giving it some reasonable name.
There are few related patterns to the Factory, which can replace this pattern:
Chain of responsibility
Dispatcher
Visitor
Dependency injection
Reflection?
There is also comment from #icza about Reflection. I agree with him, this is used commonly, and We cannot avoid the reflection in our code, because sometimes things are so dynamic.
But in your scenario it is bad solution because:
You lose compile-time type checking
You have to modify code when you are adding new type, so why not to add new line in this Factory function?
You make your code slower(see references), it adds 50%-100% lose of performance.
You make your code so unreadable and complex
You have to add a much more error handling to cover not trivial errors from reflection.
Of course, you can add a lot of tests to cover a huge number of scenarios. You can support TypeA, TypeB, TypeC in your code and you can cover it with tests, but in production code sometime you can pass TypeXYZ and you will get runtime error if you do not catch it.
Conclusion
There is nothing bad with your switch/case scenario, probably this is the most readable and the easiest way to do what you want to do.
Reference
Factory method: https://www.sohamkamani.com/golang/2018-06-20-golang-factory-patterns/
Classic book about patterns in programming: Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma and his band of four, ISBN: 978-0201633610
Reflection benchmarks: https://gist.github.com/crast/61779d00db7bfaa894c70d7693cee505

"Generate resource" command with "--skip-model" flag results in faulty resource

Description
Just started using Buffalo, trying out all the beautiful features :)
I'm having an issue however with the "generate resource" command in combination with the "--skip-model" flag. When this flag is used, all the generated functions ("List", "Show", "Create", "Update" and "Destroy") are created fully in lowercase. The struct however that is also generated refers to "buffalo.Resource" and contains these functions with the first letter in uppercase, resulting in a resource that doesn't work.
Steps to Reproduce the Problem
Use generate resource command with "--skip-model" flag: buffalo g r todo --skip-model.
Run the application using: buffalo dev.
Navigate to "http://127.0.0.1:3000/todoes"; verify that you get an error saying "runtime error: invalid memory address or nil pointer dereference".
Verify in the generated file that "todoes.go" contains the generated functions ("List", "Show", "Create", "Update" and "Destroy") fully in lowercase, while the generated struct called "TodoesResource" refers to "buffalo.Resource" and contains these functions with the first letter in uppercase.
Expected Behavior
I expected the generated functions to have the first letter in uppercase, matching the names in "buffalo.Resource" and resulting in the response "Todo#list" when navigating to "http://127.0.0.1:3000/todoes" (after starting the application). This is the case when you don't use the "--skip-model" flag, so I'm not sure why this would behave differently when you do use this flag.
Actual Behavior
The generated functions ("List", "Show", "Create", "Update" and "Destroy") are fully in lowercase, while the generated struct called "TodoesResource" refers to "buffalo.Resource" and contains these functions with the first letter in uppercase. This results in the error "runtime error: invalid memory address or nil pointer dereference" when navigating to "http://127.0.0.1:3000/todoes" (after starting the application).
Suggested solution(s)
I'm not able to create a pull request (as I get the error "Permission to gobuffalo/buffalo.git denied" when trying to publish a branch), but I think there are two possible solutions to this issue:
Preferred solution
Modifying the file "genny/resource/templates/standard/action/resource-name.go.tmpl" to change the code below:
// {{$a.String}} default implementation.
func (v {{$.opts.Name.Resource}}Resource) {{$a.String}}(c buffalo.Context) error {
return c.Render(http.StatusOK, r.String("{{$.opts.Model.Proper}}#{{$a.String}}"))
}
And change this to:
// {{$a.Pascalize}} default implementation.
func (v {{$.opts.Name.Resource}}Resource) {{$a.Pascalize}}(c buffalo.Context) error {
return c.Render(http.StatusOK, r.String("{{$.opts.Model.Proper}}#{{$a.Pascalize}}"))
}
Alternative solution
Modifying the file "genny/resource/actions.go" to change the code below:
func actions(opts *Options) []name.Ident {
actions := []name.Ident{
name.New("list"),
name.New("show"),
name.New("create"),
name.New("update"),
name.New("destroy"),
}
if opts.App.AsWeb {
actions = append(actions, name.New("new"), name.New("edit"))
}
return actions
}
And change this to:
func actions(opts *Options) []name.Ident {
actions := []name.Ident{
name.New("List"),
name.New("Show"),
name.New("Create"),
name.New("Update"),
name.New("Destroy"),
}
if opts.App.AsWeb {
actions = append(actions, name.New("New"), name.New("Edit"))
}
return actions
}
This was a bug and is currently being fixed. Also see: https://github.com/gobuffalo/buffalo/issues/2023.

linter err113: do not define dynamic errors, use wrapped static errors instead

I am using err113 as part of golangci-lint.
It is complaining about ...
foo_test.go:55:61: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"repo gave err\")" (goerr113)
repoMock.EXPECT().Save(gomock.Eq(&foooBarBar)).Return(nil, errors.New("repo gave err")),
^
foo_test.go:22:42: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"oops\")" (goerr113)
repoMock.EXPECT().FindAll().Return(nil, errors.New("oops"))
^
What is best way to fix this ?
Quoting https://github.com/Djarvur/go-err113
Also, any call of errors.New() and fmt.Errorf() methods are reported
except the calls used to initialise package-level variables and the
fmt.Errorf() calls wrapping the other errors.
I am trying to get a idiomatic example for this.
Declare a package-level variables as suggested:
var repoGaveErr = errors.New("repo gave err")
func someFunc() {
repoMock.EXPECT().Save(gomock.Eq(&foooBarBar)).Return(nil, repoGaveErr)
}
Every call to errors.New allocates a new unique error value. The application creates a single value representing the error by declaring the package-level variable.
There are two motivations for the single value:
The application can compare values for equality to check for a specific error condition.
Reduce memory allocations (although probably not a big deal in practice)
The value io.EOF is a canonical example.
Since GO 1.13 you can define a new error type, wrap it and use it.
for example, if you want to return an "operation not permitted" + the operation.
you need to implement something like
var OperationNotPermit = errors.New("operation not permitted")
func OperationNotFoundError(op string) error {
return fmt.Errorf("OperationNotPermit %w : %s", OperationNotPermit, op)
}
then in your code, when you want to return the error,
return nil, OperationNotFoundError(Op)
Let's back to question case:
first, define the custom error and the wapper
var repoError = errors.New("repositoryError")
func RepositoryError(msg string) error {
return fmt.Errorf("%w: %s", repoError,msg)
}
then in your code,
repoMock.EXPECT().Save(gomock.Eq(&foooBarBar)).Return(nil, RepositoryError("YOUR CUSTOM ERROR MESSAGE"))
Since it hasn't been said before, you probably don't need to define package level errors for tests. Given the idea is to wrap errors so they can be compared and unwrapped in the caller, returning a dynamic error in a test is fine as long as the purposes of your test are served.

Why do I need to use pipe with shareReplay?

I'm confused why the following doesn't compile:
login(email: string, password: string): Observable<any> {
return this.http.post<authObj>('/users/login',{"email": email, "password": password})
.shareReplay()
}
(I get error Property 'shareReplay' does not exist on type 'Observable<authObj>'.ts(2339)).
Note for reference: authObj is:
interface authObj {
"username": string,
"email": string
}
but the following compiles fine:
login(email: string, password: string): Observable<any> {
return this.http.post<authObj>('/users/login',{"email": email, "password": password})
.pipe(
tap(res=> this.setSession),
shareReplay()
);
}
I would have thought that from the standpoint of shareReplay both constructs are similar/identical in terms of the calling object/input, so I would have thought either of the above should work. Why does only the second construct compile? Tks!
The biggest change from RxJs 5 to 6 was tree shakability. In RxJs 5 all the operators were part of the observable class. This means that when you import the Observable class into your project you get the lot, every operator no matter if your were using them or not. This tends to bloat the size of your application even if you only need a small amount of RxJs functionality.
With RxJs 6 the operators were moved out into pipeable functions, this meant that you could no longer use the fluent syntax of the methods but came with the huge advantage of your build only needing to include the functionality you require rather than all of RxJs. This reduced build sizes dramatically.
For more information take a look at:
Official documentation about "Pipeable Operators"

Resources