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

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.

Related

K8s Operator listen to secret change with event filter

We have created a few month ago controller which runs great using kubebuilder.
Few weeks ago we added a “listener” to a secret which when the secret is changing (secret properties)
The reconcile should be invoked, the problem is that it is sometimes working and sometimes not, (you change the secret apply it and the reconcile doesn’t happens) , we are doing it for the exact same secret file.
We tried few days to find the root cause without success, (we change the k8s.io/client-go v0.23.4 and also to v0.22.3 and now v0.22.1 that is only working.
Any idea what the issue could be? any hint will be helpful. or Any other way to do it that we can try out.
func (r *vtsReconciler) SetupWithManager(mgr ctrl.Manager) error {
manager := ctrl.NewControllerManagedBy(mgr).
For(&vts.str).
WithEventFilter(predicate.Or(predicate.AnnotationChangedPredicate{}))
manager = manager.Watches(&source.Kind{Type: &v1.Secret{}}, handler.EnqueueRequestsFromMapFunc(func(a client.Object) []reconcile.Request {
return r.SecretRequests.SecretFinder(a.GetName())
}))
return manager.Complete(r)
}
func (secm *SecretMapper) SecretFinder(name string) []reconcile.Request {
v := cli.ObjectKey{Name: name}
return secm.SecMap[v.String()]
}
Most likely the issue is that the WithEventFIlter applies to all watched objects by the controller. The generation is auto-incremented for CRDs, but this doesn't hold for all resource types.
From the GenerationChangedPredicate docs:
// Caveats:
//
// * The assumption that the Generation is incremented only on writing to the spec does not hold for all APIs.
// E.g For Deployment objects the Generation is also incremented on writes to the metadata.annotations field.
// For object types other than CustomResources be sure to verify which fields will trigger a Generation increment when they are written to.
You can check this by creating a secret / updating a secret you will see that there is no generation set (at least not on my local k3d cluster).
Most likely it works for the creation as initially the controller will sync existing resources with the cluster.
To solve it you can use:
func (r *vtsReconciler) SetupWithManager(mgr ctrl.Manager) error {
manager := ctrl.NewControllerManagedBy(mgr).
For(&vts.str, WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}))).
manager = manager.Watches(&source.Kind{Type: &v1.Secret{}}, handler.EnqueueRequestsFromMapFunc(func(a client.Object) []reconcile.Request {
return r.SecretRequests.FindForSecret(a.GetNamespace(), a.GetName())
}))
return manager.Complete(r)
}
which should apply the predicates only to your custom resource.

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.

AWS Step Function error handling for Go Lambda

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.

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.

catching Enter in a multiline Fyne Entry widget (more generally, calling "parent classes")

This question is more about Go than Fyne. Having extended Fyne's Entry widget is the prescribed fashion, I want to detect when Enter (Return) is pressed and use it as a signal that I need to act on the content of the Entry. I want Shift-Return to add a newline to the text without signalling that I need to act.
Given a struct that starts with
type myEntry struct {
widget.Entry
.....more... }
It's easy enough to add
func (m *myEntry) TypedKey(key *fyne.KeyEvent) {
if key.Name == "Return" {
///send m.Text somewhere...
} else {
//WRONG: m.(*widget.Entry).TypedKey(key) //give Key to Entry widget to process
}
}
but the else clause doesn't compile. So after having decided this isn't a Key I want to intercept, how do I give it back to widget.Entry? Other questions here about calling "base classes", which Go doesn't quite have, don't seem to cover this case.
I thought I could finesse this by adding
type myEntry struct {
widget.Entry
me *Fyne.Focusable
and setting me to the address of myEntry on creation, so I could simply call me.TypedKey. But keys were not handled, and then there was a crash. Setting me=&myNewEntryObject on creation apparently isn't sufficiently "widget.Entry-like" to win the day.
I know Go isn't an OO language, but extending a type and then redirecting calls back to the parent type is a fundamental programming technique; I'd go as far as saying there's no point in extending a struct if you can't get back to the "base struct's" behaviour from the extension. What am I missing?
Embedded types without a name can be referenced using the name of the type - so the following will work:
func (m *myEntry) TypedKey(key *fyne.KeyEvent) {
if key.Name == "Return" {
// send m.Text somewhere...
} else {
Entry.TypedKey(key)
}
}

Resources