06 - Creating Signup Handler in Gin - Binding Data
Tags: go, testing, tutorial, gin
Published: 2020-10-30 02:22:11

In this tutorial we'll get started on a handler for signing up a user. More specifically, we'll work on binding the JSON request body to a struct. In the process, we'll create reusable code for sending the API consumer validation errors. 

We'll also add a new method to our UserService for signing up a user. We'll make sure to include this method in our mock as well so that we can begin adding our first tests for this method.

In the next part, we'll create a _TokenService_ for creating tokens when a user successfully signs up or signs in.

If at any point you are confusion about file structure or code, go to [Github repository](https://github.com/JacobSNGoodwin/memrizr) and checkout the branch for the previous lesson to be in sync with me!

If you prefer video, check out the video version below!
{% youtube zNWfAe9JJ54 %}

And for an overview of what the app we're building, see this.
{% youtube Ez2dOyxmGDE %}

## Diagram

The diagram below shows the handler for the POST endpoint we'll be working on. As previously mentioned, this handler will interact with a UserService and TokenService, as shown.

![Signup Handler](https://dev-to-uploads.s3.amazonaws.com/i/ytwwuvra8pilunx5g01i.png)

## Fixes from last time

If you look at the go.mod file, we currently have the following name for our module: `module github.com/jacobsngoodwin/memrizr`. However, I would like to append the path with _account_, to correspond to the path of our current golang application. The result will be `module github.com/jacobsngoodwin/memrizr/account`.

You'll just need to make sure you update the import paths in all files to include the addition of _account_ to the module path.

I'd also recommend re-running `go test ./...` inside of the account folder to make sure all tests are passing. 

## Create Signup Handler

Let's add a file for our signup handler, `~/handler/signup.go`. The Signup handler method is currently contained inside of `~/handler/handler.go`.  Cut _Signup_ from this file and add it to our newly created file. Upon completion, we should have the following in our _signup.go_. Also make sure to include `package handler` and relevant imports at the top of the file. 

```go
// Signup handler
func (h *Handler) Signup(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"hello": "it's signup",
	})
}
```

Alright, now it's time for the fun. I highly recommend you find your favorite caffeinated beverage and settle down for the long haul!

## Incoming Request Body Definition

Inside of _signup.go_, we add the following.

```go
// signupReq is not exported, hence the lowercase name
// it is used for validation and json marshalling
type signupReq struct {
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,gte=6,lte=30"`
}

// Signup handler
func (h *Handler) Signup(c *gin.Context) {
	// define a variable to which we'll bind incoming
	// json body, {email, password}
	var req signupReq

	//
}
```

The _signupReq_ struct defines the fields we expect to receive on the incoming request. We'll eventually bind this data to an instance of this struct, which is defined inside of the handler by `var req signupReq`.

Inside of the struct, you'll see struct "tags" defined inside of back-ticks. 

The "json" struct tags bind the name of our struct parameters (e.g. **Email**) to values received in the request body as JSON (e.g. **email**). Because we've included these JSON struct tags, the gin framework will be able to handle binding the incoming serialized request body to our struct instance. 

The "binding" struct tags are used to set validation rules. Gin comes preloaded with the [go-playground/validator](https://github.com/go-playground/validator) library which includes several pre-defined validators for incoming request bodies, and for things like headers as well. In our case, we require both email and password to be non-empty, and the password must be between 6 and 30 characters, inclusive.

The [documentation](https://pkg.go.dev/github.com/go-playground/validator/v10) lists built-in validators and also describes things like validation errors and custom validators. Note that in the documentation, the struct tag `validate` is used, whereas in Gin we must use the struct tag `binding`.

> Make sure throughout this project to import go-playground/validator/10. I had some issues with auto-import importing v9

## Binding Data

The [gin documentation](https://github.com/gin-gonic/gin) has excellent documentation on how to bind incoming request data to a struct. The framework provides various methods on its context (gin.Context) which allow us to bind data to structs like the _signupReq_ which we just created.
 
There are specific methods for binding different incoming HTTP request bodies such as JSON, XML, or form data. We're going to use a method called "shouldBind" which infers which binder to use from the `Content-Type` header of the incoming request. Since we only have struct tags for "json," our shouldBind will only know how to work with JSON, even if we were to send a request with a different Content-Type header.

We'll now create a helper function, _bindData_ which we'll use along with gin and go-playground/validator to bind data to structs and respond with any validation or data-binding error.

Since we'll be reusing this function, let's create a new file for this function, `~/handler/bind_data.go`, making sure to include the package name and imports corresponding to your module path.

```go
// used to help extract validation errors
type invalidArgument struct {
	Field string `json:"field"`
	Value string `json:"value"`
	Tag   string `json:"tag"`
	Param string `json:"param"`
}

// bindData is helper function, returns false if data is not bound
func bindData(c *gin.Context, req interface{}) bool {
	// Bind incoming json to struct and check for validation errors
	if err := c.ShouldBind(req); err != nil {
		log.Printf("Error binding data: %+v\n", err)

		if errs, ok := err.(validator.ValidationErrors); ok {
			// could probably extract this, it is also in middleware_auth_user
			var invalidArgs []invalidArgument

			for _, err := range errs {
				invalidArgs = append(invalidArgs, invalidArgument{
					err.Field(),
					err.Value().(string),
					err.Tag(),
					err.Param(),
				})
			}

			err := apperrors.NewBadRequest("Invalid request parameters. See invalidArgs")

			c.JSON(err.Status(), gin.H{
				"error":       err,
				"invalidArgs": invalidArgs,
			})
			return false
		}

		// later we'll add code for validating max body size here!

		// if we aren't able to properly extract validation errors,
		// we'll fallback and return an internal server error
		fallBack := apperrors.NewInternal()

		c.JSON(fallBack.Status(), gin.H{"error": fallBack})
		return false
	}

	return true
}

```

### bindData Explanation

In this helper function we do the following:

1. Create an `invalidArgument` type with "json" struct tags. This will enable us to send the selected validation information in a JSON response.
2. Attempt to bind the data to the `req` argument which we passed into the function. This req will be passed by reference.
3. We determine if the binding error can be cast as `validator.ValidationErrors` from _go-playground/validate_. 
4. If the error is a `validator.ValidationErrors`, we extract 4 fields of interest: Field, Value, Tag, and Parameter using methods from the library.
5. We create a `NewBadRequest` error from our custom _apperrors_ package and send it, along with the `invalidArgument`s we extracted, as JSON. 
6. If the binding error is something other than `validator.ValidationErrors`, we send a fallback 500 error.
7. In either error case, we return false from the function so that the calling handler will know that a JSON response has already been sent, and that binding failed.
8. If there are no errors, we return true to let the calling function know binding was successful. 

### Call bindData In Signup Handler

Finally, let's call this in _signup.go_. Note that in the cases of an error, or `!ok`, we return to prevent overwriting the JSON response in `bindData`.  

```go
// Bind incoming json to struct and check for validation errors
	if ok := bindData(c, &req); !ok {
		return
	}
```

## Add Signup to UserService

#### Interface Method Definition

The service layer will eventually need to implement the logic for signing up a user. So let's think about what we expect our Signup method to do. We've already made part of that decision by only accepting an email and password in the signup handler. We could have also accepted more information like name and imageURL, but we'll handle updating user information in a separate method.

So here's what we'll add to our interface in `~/model/interfaces.go`.

```go
type UserService interface {
	Get(ctx context.Context, uid uuid.UUID) (*User, error)
	Signup(ctx context.Context, u *User) error
}
```

We'll require the method to accept a `*model.User`, though the user will only have email and password assigned when called. The method possibly returns an error from down the call chain (service or repository layer). 

You might ask why we don't return a `model.User`. We make the choice here to modify the `*User` reference passed as a parameter. In this case, we'll modify the user by adding an ID down the call chain.

#### Call Signup in Handler

Back in _signup.go_, let's add the following below the call to _bindData_.

```go
u := &model.User{
		Email:    req.Email,
		Password: req.Password,
	}

	err := h.UserService.Signup(c, u)

	if err != nil {
		log.Printf("Failed to sign up user: %v\n", err.Error())
		c.JSON(apperrors.Status(err), gin.H{
			"error": err,
		})
		return
	}
```

This does the following:

1. Creates a new reference to a user by assigning the user `Email` and `Password` from the successfully bound request.
2. Calls our recently defined `Signup` method. If we get an error from the user service, we'll return that error as JSON. Note that we're using our `apperrors.Status` function to determine if the error can be cast to one of our custom errors, and then returning the appropriate status code. I'll leave it to you to review that method if you want!
  
At this point you may be getting some warnings from your editor. This is because both our concrete and mock implementations of the UserService do not yet implement the `Signup` method.

Let's fix that.

## Implement UserService.Signup Method

#### Application Implementation

Let's first add the Signup method to the concrete implementation in `~/service/user_service.go`. We'll add some real code in this method down the line, but for now, just panic if the method is called.

```go
// SignUp reaches our to a UserRepository to verify the
// email address is available and signs up the user if this is the case
func (s *UserService) Signup(ctx context.Context, u *model.User) error {
	panic("Method not implemented")
}
```

#### Mock Implementation

I'll include the mock implementation here without explanation. See the [previous](https://dev.to/jacobsngoodwin/04-testing-first-gin-http-handler-9m0) two tutorials or the [testify documentation](https://github.com/stretchr/testify) for more information!

```go
// Signup is a mock of UserService.Signup
func (m *MockUserService) Signup(ctx context.Context, u *model.User) error {
	ret := m.Called(ctx, u)

	var r0 error
	if ret.Get(0) != nil {
		r0 = ret.Get(0).(error)
	}

	return r0
}
```

Hopefully by now, any editor errors (at least regarding UserService implementations) have been eliminated.

## Signup Test

Create a file `~/handler/signup_test.go`. Inside, add the outer function for the test and configure gin in test mode.

```go
func TestSignup(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)
}
```

So what should we test with our partially implemented Signup handler?

For now, we'll simply test that we get a proper status code of 400 for a few validation error cases. We'll also test that we get the proper response in the case of an error calling the UserService. 

#### Test Missing Email and Password

In this cases, we set up our mock UserService. We don't care what it returns as we expect bindData to return an error response before this method can be called. We merely need this mock so we can assert that it is _not called._

We then instantiate our handler and a response recorder, `rr`, from the _httptest_ package.

Next, we create a request body with an empty email address and missing password. 

Then we serve this request to our handler, and assert that a status code of 400 is received, and that we do not reach the part of our function where `UserService.Signup` is called. 

We could break this up into more tests, but this tutorial is already long enough. 

```go
t.Run("Email and Password Required", func(t *testing.T) {
		// We just want this to show that it's not called in this case
		mockUserService := new(mocks.MockUserService)
		mockUserService.On("Signup", mock.AnythingOfType("*gin.Context"), mock.AnythingOfType("*model.User")).Return(nil)

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// don't need a middleware as we don't yet have authorized user
		router := gin.Default()

		NewHandler(&Config{
			R:           router,
			UserService: mockUserService,
		})

		// create a request body with empty email and password
		reqBody, err := json.Marshal(gin.H{
			"email": "",
		})
		assert.NoError(t, err)

		// use bytes.NewBuffer to create a reader
		request, err := http.NewRequest(http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")

		router.ServeHTTP(rr, request)

		assert.Equal(t, 400, rr.Code)
		mockUserService.AssertNotCalled(t, "Signup")
	})
```

#### Invalid Email

This is very similar to above, but we change the request body to have an invalid email, but valid password.

```go
t.Run("Invalid email", func(t *testing.T) {
  ...

		// create a request body with empty email and password
		reqBody, err := json.Marshal(gin.H{
			"email":    "bob@bo",
			"password": "avalidpassword123",
		})
		
  })
  
  ....
```

####  Password to Short or Long

Again, we can duplicate the above tests, but alter the request body to have a valid email, but invalid passwords.

```go
t.Run("Password too short", func(t *testing.T) {
    ...

		// create a request body with empty email and password
		reqBody, err := json.Marshal(gin.H{
			"email":    "bob@bob.com",
			"password": "inval",
		})
    
    ...
	})
```

...And for a long password...

```go
	t.Run("Password too long", func(t *testing.T) {
    ...
    
		// create a request body with empty email and password
		reqBody, err := json.Marshal(gin.H{
			"email":    "bob@bob.com",
			"password": "invalkadsfjasdfkj;askldfj;askldfj;asdfiuerueuuuuuudfjasdfasdkfjj",
		})
    
    ...
```

#### Error Returned by UserService.Signup

In this case we create a `model.User` reference that will be used for calling the Signup method. We also tell the response to respond with a conflict error having Status Code 409, which would happen if the user already exists.

Make sure to change the return of the mock to return this `NewConflict` error. 

```go
t.Run("Error calling UserService", func(t *testing.T) {
		u := &model.User{
			Email:    "bob@bob.com",
			Password: "avalidpassword",
		}

		mockUserService := new(mocks.MockUserService)
		mockUserService.On("Signup", mock.AnythingOfType("*gin.Context"), u).Return(apperrors.NewConflict("User Already Exists", u.Email))

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// don't need a middleware as we don't yet have authorized user
		router := gin.Default()

		NewHandler(&Config{
			R:           router,
			UserService: mockUserService,
		})

		// create a request body with empty email and password
		reqBody, err := json.Marshal(gin.H{
			"email":    u.Email,
			"password": u.Password,
		})
		assert.NoError(t, err)

		// use bytes.NewBuffer to create a reader
		request, err := http.NewRequest(http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
		assert.NoError(t, err)

		request.Header.Set("Content-Type", "application/json")

		router.ServeHTTP(rr, request)

		assert.Equal(t, 409, rr.Code)
		mockUserService.AssertExpectations(t)
	})
```

#### Run Package Tests

`go test -v ./handler`

If all goes well, you should receive the following (in addition to information for TestMe).

```sh
--- PASS: TestSignup (0.00s)
    --- PASS: TestSignup/Email_and_Password_Required (0.00s)
    --- PASS: TestSignup/Invalid_email (0.00s)
    --- PASS: TestSignup/Password_too_short (0.00s)
    --- PASS: TestSignup/Password_too_long (0.00s)
    --- PASS: TestSignup/Error_calling_UserService (0.00s)
```

Before continuing, I recommend that you run the tests with different status codes, or request bodies, to make sure they fail properly.

## Next Time

Thanks for sticking around for this somewhat tedious tutorial! I do believe this information is useful, and hope you can apply these principles in your daily work. 

Next time we'll wrap up this handler by creating and calling a TokenService.