I have struct that have a method that serve the response.
type ctrl struct {
*base.AjaxCtrl
file ini.File
}
func (rcv *ctrl) getVar() string {
return mux.Vars(rcv.Req)["location"]
}
func (rcv *ctrl) getFile() string {
return location.JoinPaths(folder, rcv.getVar()+ext)
}
func (rcv *ctrl) upload() {
file, err := ini.LoadFile(rcv.getFile())
if err != nil {
rcv.AddErr("TextError", err.Error())
return
}
rcv.file = file
}
// Convert text to json structure
func (rcv *ctrl) convertToJson() string {
js, err := json.Marshal(rcv.file.Section("text/signup"))
if err != nil {
rcv.AddErr("ConvertError", err.Error())
return ""
}
return string(js)
}
func (rcv *ctrl) serveHttp() (types.SuccsJSON, types.ErrorsJSON) {
rcv.upload()
if rcv.AnyErrors() {
return nil, rcv.Errs
}
str := rcv.convertToJson()
if rcv.AnyErrors() {
return nil, rcv.Errs
}
return c.Sucss, nil
}
The method serveHttp() handle the response to client. As you can see, I am handling the error with the method AnyError() everytime. I find this way is pretty boring and maybe wrong design.
Would it be better to throw a panic instead of error handling?
func (rcv *ctrl) upload() {
file, err := ini.LoadFile(rcv.getFile())
if err != nil {
rcv.AddErr("TextError", err.Error())
panic()
}
rcv.file = file
}
No, it would not be better. Checking your errors is not bad design in Go. Panicking when there is no reason to do so, on the other hand, is bad design, because it's harder to test and debug.
The Go Wiki explicitly discourages the use of panic outside of truly exceptional cases, when the error is unrecoverable and state of the program can't be fixed.
Related
I am trying to do dependency injection in golang with applying dependency inversion principle, so I have the following service
package account
import (
types "zaClouds/modules/account/domain/types"
"zaClouds/modules/shared"
)
type IPlanDomainService interface {
GetUsagePlanById(string) *shared.Result[types.UsagePlan]
}
type PlanDomainService struct {
usagePlanService types.IUsagePlanService
}
func (planDomainService *PlanDomainService) GetUsagePlanById(id string) *shared.Result[types.UsagePlan] {
result := &shared.Result[types.UsagePlan]{}
usagePlanResult := planDomainService.usagePlanService.GetPlanById(id)
if usagePlanResult.Err != nil {
result.Err = usagePlanResult.Err
return result
}
result.Data = usagePlanResult.Data
return result
}
func PlanDomainServiceFactory(usagePlanService types.IUsagePlanService) IPlanDomainService {
return &PlanDomainService{usagePlanService: usagePlanService}
}
as you can see, it accepts another service with type IUsagePlanService
and here is the interface for it
package account
import (
"zaClouds/modules/shared"
"github.com/shopspring/decimal"
)
type UsagePlan struct {
ID string
Title string
Description interface{}
PlanID string
Price decimal.Decimal
Duration int
Features map[string]map[string]string
}
type IUsagePlanService interface {
GetPlanById(string) *shared.Result[UsagePlan]
}
and here is the way I am injecting this service to domain service
func DiInit(usagePlanService interface{}) domainServices.IPlanDomainService {
domainServices.PlanDomainServiceFactory(types.IUsagePlanService(usagePlanService))
return domainServices.PlanDomainServiceFactory(usagePlanService.(types.IUsagePlanService))
}
as you can see, I am trying to do a type assertion but it doesn't work, and gives me the following error:
panic: interface conversion: *usagePlan.UsagePlanRepository is not account.IUsagePlanService: missing method GetPlanById
Edit
Here is the actual implementation for usagePlanService
type IUsagePlanRepository interface {
createClient(string) *http.Request
GetPlanById(string) *shared.Result[usagePlanRepoModels.UsagePlan]
}
type UsagePlanRepository struct {
plansEndpoint string
httpClient *http.Client
}
func (r *UsagePlanRepository) GetPlanById(id string) *shared.Result[usagePlanRepoModels.UsagePlan] {
result := &shared.Result[usagePlanRepoModels.UsagePlan]{}
req := r.createClient(id)
resp, err := r.httpClient.Do(req)
if err != nil {
log.Println("failed to load plan details \n[ERROR]", err)
result.Err = err
return result
}
defer func() {
bodyError := resp.Body.Close()
if bodyError != nil {
result.Err = bodyError
}
}()
if result.Err != nil {
return result
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
utils.Logger.Error("failed to load plan details \n[ERROR]", err, nil)
result.Err = err
return result
}
if resp.StatusCode >= 400 {
result.Err = errors.New(string(body))
utils.Logger.Info("getPlanById", string(body))
}
getUsagePlanResponse, foundError := usagePlanRepoModels.CreateGetUsagePlanResponse(body)
if foundError != nil {
result.Err = foundError
return result
}
result.Data = *getUsagePlanResponse
return result
}
When using an interface, you need to define all functions that you will use with the same name and signature as the implementation.
The error message you got indicates that the implementation and the interface are different.
The implementation is not shown in your question, but you defined the function for your interface like this: GetPlanById(string) *shared.Result[UsagePlan]. Any deviation from it will result in error. One common mistake is with the pointers. Adding or removing the * to the return type will incur in error if it differs from the original.
Edit:
Your interface should look like this:
type IUsagePlanService interface {
GetPlanById(id string) *shared.Result[usagePlanRepoModels.UsagePlan]
}
If your function is returning a private type, and you can change that, you should. If you cant change it, than you should create a function that wraps the function you are trying to abstract with the interface.
I have a function with this signature in Go:
func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error)
It returns an error if there's an internal error (like the database query fails for some reason), but I'm not sure what I should return if the account is not found. I can think of two different approaches:
Just return a nil account and nil error if no account is found
Return a custom error type like this:
type accountNotFoundErr struct {
id uuid.UUID
}
func (err accountNotFoundErr) Error() string {
return fmt.Sprintf("account not found for user: %v", err.id)
}
func IsAccountNotFoundErr(err error) bool {
_, ok := err.(accountNotFoundErr)
return ok
}
func GetAccount(ctx context.Context, id uuid.UUID) (*Account, error) {
// if the account is not found
return nil, accountNotFoundErr{id}
}
I like the first one because it's simple, but I don't often see Go code which returns a nil result if the error is non-nil. I think the expectation is that, if the error is nil, the result is valid. The second approach fixes that, but it's also a bit more complicated for callers.
What is an idiomatic approach for handling cases like this in Go?
I have read a lot of posts about custom errors in go. Most of them created their own struct that implements the error interface.
The issue I found with that approach was that I did not manage to easily check if an error was of a certain type. The same way, you may be able to check some std lib error like if error == EOF.
Therefore, my favourite way to do that is creating a simple var with erros.New.
var ErrNotFound = errors.New("Resource was not found")
func main() {
err := raise()
if err == ErrNotFound {
fmt.Println("impossibru")
return
}
if err != nil {
fmt.Println("unexpected error")
return
}
}
func raise() error {
return ErrNotFound
}
https://play.golang.com/p/s0ZQfsdLqxB
As #Gavin pointed out in the comments, if you want to provide more context to the error by wrapping it with fmt.Errorf, you need to use errors.Is to check if the specific error was wrapped.
var ErrNotFound = errors.New("Resource was not found")
func main() {
err := raise(42)
if errors.Is(err, ErrNotFound) {
fmt.Println(err)
return
}
if err != nil {
fmt.Println("unexpected error")
return
}
}
func raise(id int) error {
return fmt.Errorf("id %d does not exist, error: %w", id, ErrNotFound)
}
https://play.golang.com/p/hSrkb1Xp4Hn
I pasted a section of code that was supposed to catch an AllTopologyNodesDownError error which doesn't work and I have no idea why.
func (sc *ServerConfig) addNodesToCluster(store *ravendb.DocumentStore) error {
clusterTopology, err := sc.getClusterTopology(store)
if errors.Is(err, &ravendb.AllTopologyNodesDownError{}) {
for _, url := range sc.Url.List {
err = addNodeToCluster(store, url)
if err != nil {
return err
}
}
} else if err != nil {
return err
}
the structure of the ravendb.AllTopologyNodesDownError is
// AllTopologyNodesDownError represents "all topology nodes are down" error
type AllTopologyNodesDownError struct {
errorBase
}
type errorBase struct {
wrapped error
ErrorStr string
}
screen shot of the error when debugging the code
errors.Is() is used to tell if any error in the chain is the same instance as the provided error1, that can never be the case here because you provided a literal of your error type, no other code could hold that instance or a reference to it.
Your error looks like a type, to tell if any error in the chain is a given type you should use errors.As():
clusterTopology, err := sc.getClusterTopology(store)
var errAllDown *AllTopologyNodesDownError
if errors.As(err, &errAllDown) {
// err had an *AllTopologyNodesDownError in its
// chain and errAllDown now contains it.
}
Can be overridden by implementing the Unwrap() interface which your error type does not.
I'm trying to build a method that, using the Kubernetes client-go library, fetches and returns the actual Resources for a given *metav1.OwnerReference. I have this:
func fetchResource(ref *metav1.OwnerReference, options *RequestOptions) (*metav1.ObjectMeta, error) {
switch ref.Kind {
case "ReplicaSet":
return options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
case "Deployment":
return options.Clientset.AppsV1().Deployments(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
case "Job":
fallthrough
// more stuff...
default:
return nil, nil
}
}
This code does not compile because:
cannot use options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, (metav1.GetOptions literal)) (value of type *"k8s.io/api/apps/v1".ReplicaSet) as *"k8s.io/apimachinery/pkg/apis/meta/v1".ObjectMeta value in return statement
My guess was that since the documentation says that basically all resources embedd the metav1.ObjectMeta, I could use it as a return type.
I tried creating and returning an interface instead, but realized I can't implement it for types outside my package:
type K8sResource interface {
Name() string
Kind() string
OwnerReferences() []metav1.OwnerReference
}
func (pod *corev1.Pod) Name() string {
return pod.Name
}
func (pod *corev1.Pod) Kind() string {
return pod.Kind
}
func (pod *corev1.Pod) OwnerReferences() []metav1.OwnerReference {
return pod.OwnerReferences
}
This code does not compile because:
invalid receiver *"k8s.io/api/core/v1".Pod (type not defined in this package)
What would be the idiomatic and correct solution here?
If you want to return the imported types as an interface that they don't already implement, you can wrap them in types that do implement it.
For example:
type K8sResource interface {
Name() string
Kind() string
OwnerReferences() []metav1.OwnerReference
}
type replicaSet struct{ *v1.ReplicaSet }
func (s replicaSet) Name() string {
return s.ReplicaSet.Name
}
func (s replicaSet) Kind() string {
return s.ReplicaSet.Kind
}
func (s replicaSet) OwnerReferences() []metav1.OwnerReference {
return s.ReplicaSet.OwnerReferences
}
func fetchResource(ref *metav1.OwnerReference, options *RequestOptions) (K8sResource, error) {
switch ref.Kind {
case "ReplicaSet":
res, err := options.Clientset.AppsV1().ReplicaSets(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return replicaSet{res}, nil // wrap it up
case "Pod":
res, err := options.Clientset.AppsV1().Pods(options.Namespace).Get(options.Context, ref.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return pod{res}, nil // wrap it up
case "Job":
fallthrough
// more stuff...
default:
return nil, nil
}
}
What's the best signature for a function that returns an optional value and a possible error?
For example:
func findColor(name string) (RGB, error) {
...
}
(The empty RGB value is black, a valid color, so you can't use it to infer that no value was found. Assume the error might come from something like a database connection.)
The two options that seem best are a boolean return value:
func findColor(name string) (RGB, bool, error) {
...
}
c, ok, err := findColor(myname)
if !ok {
...
} else if err != nil {
...
}
...
Or a special error value:
var ColorNotFound = errors.New(...)
func findColor(name string) (RGB, error) {
...
}
c, err := findColor(...)
if err == ColorNotFound {
...
} else if err != nil {
...
}
...
(Making special errors seems like a pain.)
What's the most idiomatic approach?
The convention in Go is to return (value, error) and if error != nil then value is (or may be) invalid.
If you have special errors you need to do something with (like io.EOF) then making a specific error is normal practice. So I would say your 3rd example is the most idiomatic, if you want to do something different for ColorNotFound.
var ColorNotFound = errors.New(...)
func findColor(name string) (RGB, error) {
// ...
}
c, err := findColor(...)
if err == ColorNotFound {
// Do something special if ColorNotFound...
} else if err != nil {
// Some other kind of error...
}
You could make findColor return *RGB and then compare it to nil:
c, err := findColor(name)
if err != nil { /* Handle error. */ }
if c == nil { /* Handle no color. */ }
This is unsafe though, since if you try to call methods on a nil pointer, they can cause a panic.
I'd recommend sticking with a special ErrColorNotFound approach.