Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically run code before every function

Tags:

go

I would like to run some code (authorization check) at the start of many functions, such as:

func (s *apiService) get(request Request) (Result, error) {
  err := authorizationCheck(request)
  if err != nil {
    return nil, err
  }
  //method logic
}

func (s *apiService) put(request Request) (Result, error) {
  err := authorizationCheck(request)
  if err != nil {
    return nil, err
  }
  //method logic
}

Is there an elegant way to avoid doing the same authorization check at the start of every function?

like image 671
daniely Avatar asked Sep 15 '25 13:09

daniely


1 Answers

With a simple wrapper

Since all the methods (at least I assume so) have the same signature, you can put all the redundant code inside a wrapper function which takes the function you need to run as an additional argument. The wrapper will first check for the error and then run the function. As a result, your methods will only need to have relevant code inside, without bothering to check for the error first.

Here's an example, I called the wrapper wrap just to make it clear:

func (s *apiService) get(request Request) (Result, error) {
    //method logic
}

func (s *apiService) put(request Request) (Result, error) {
    //method logic
}

func wrap(f func (Request) (Result, error), request Request) (Result, error) {
    err := authorizationCheck(request)
    if err != nil {
        return nil, err
    }

    return f(request)
}

Then, later in your code:

res, err := wrap(s.get, someRequest)

With a decorator

Very similar to the above, but cleaner: instead of creating a wrapper you can implement a decorator which returns a function which wraps your methods and does the error checking before calling them. This again can only be done if all the methods have the same signature, but it is more powerful and IMHO a cleaner solution than using a wrapper.

Here's an example, the decorator is decorate (yay to my originality):

func (s *apiService) get(request Request) (Result, error) {
    //method logic
}

func (s *apiService) put(request Request) (Result, error) {
    //method logic
}

func decorate(f func(Request) (Result, error)) func(Request) (Result, error) {
    return func(r Request) (Result, error) {
        err := authorizationCheck(r)
        if err != nil {
            return nil, err
        }

        return f(r)
    }
}

Then, later in your code:

res, err := decorate(s.get)(someRequest)

Methods or plain functions?

Whether you prefer a simple wrapper or a decorator, you can also make them methods of your apiService object if you want (just by adding (s *apiService) before their name), there really is no big difference, but this would be the preferred option if you want your struct to have the wrapper/decorator available wherever you use it and not just in that particular file.

The relative calls would then become:

res, err := s.wrap(s.get, someRequest)

and:

res, err := s.decorate(s.get)(someRequest)
like image 149
Marco Bonelli Avatar answered Sep 18 '25 09:09

Marco Bonelli