Buy

Validate that Token Resource

It's finally that time: to add a little bit of validation to our token.

How We Validated Programmer

This should start to feel easy because we did all of this before with our programmer. Now let me remind you, in the PogrammerController, to validate things we call this validate() function, which is something I created for this project before we even started:

159 lines src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
... lines 1 - 36
public function newAction(Request $request)
... lines 38 - 43
if ($errors = $this->validate($programmer)) {
$this->throwApiProblemValidationException($errors);
}
... lines 47 - 56
}
... lines 58 - 159

But the way the validation works is that on our Programmer class we have these @Assert things:

52 lines src/KnpU/CodeBattle/Model/Programmer.php
... lines 1 - 5
use Symfony\Component\Validator\Constraints as Assert;
... lines 7 - 11
class Programmer
... lines 13 - 17
/**
* @Assert\NotBlank(message="Please enter a clever nickname")
* @Serializer\Expose
*/
public $nickname;
... lines 23 - 50
}

So when we call the validate() function on our controller, it reads this and makes sure the nickname isn't blank. It's as simple as that!

Now if that validate() function returns an array that has at least one error in it, then we'll call this throwApiProblemValidationException function, which is something that we created inside this controller. You can see it further down inside this same file:

159 lines src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
... lines 1 - 147
private function throwApiProblemValidationException(array $errors)
{
$apiProblem = new ApiProblem(
400,
ApiProblem::TYPE_VALIDATION_ERROR
);
$apiProblem->set('errors', $errors);
throw new ApiProblemException($apiProblem);
}
... lines 158 - 159

What does it do? No surprises, it creates a new ApiProblem object, sets the errors as a nice property on it then throws a new ApiProblemException. We can see this if we look inside the programmer.feature class. I'll search for 400 because our validation errors return a 400 status code:

130 lines features/api/programmer.feature
... lines 1 - 24
Scenario: Validation errors
... lines 26 - 33
Then the response status code should be 400
... lines 35 - 40
And the "errors.nickname" property should exist
But the "errors.avatarNumber" property should not exist
And the "Content-Type" header should be "application/problem+json"
... lines 44 - 130

You can see this is an example of us testing our validation situation. We're checking to see that there are nickname and avatarNumber properties on errors.

Validating ApiToken

The ApiToken class also has one of these not blank things on it:

34 lines src/KnpU/CodeBattle/Security/Token/ApiToken.php
... lines 1 - 4
use Symfony\Component\Validator\Constraints as Assert;
class ApiToken
{
... lines 9 - 14
/**
* @Assert\NotBlank(message="Please add some notes about this token")
*/
public $notes;
... lines 19 - 32
}

So, all we need to do in our controller is call these same methods. First, let's move that throwApiProblemValidationException into our BaseController, because that's going to be really handy. And of course we'll make it protected so we can use it in the sub classes:

301 lines src/KnpU/CodeBattle/Controller/BaseController.php
... lines 1 - 289
protected function throwApiProblemValidationException(array $errors)
{
$apiProblem = new ApiProblem(
400,
ApiProblem::TYPE_VALIDATION_ERROR
);
$apiProblem->set('errors', $errors);
throw new ApiProblemException($apiProblem);
}
... lines 300 - 301

Perfect!

Next, let's steal a little bit of code from our ProgrammerController and put that into our TokenController. So once we're done updating our token object, we'll just call the same function, pass it the token instead of the programmer and throw that same error:

36 lines src/KnpU/CodeBattle/Controller/Api/TokenController.php
... lines 1 - 16
public function newAction(Request $request)
... lines 18 - 25
$errors = $this->validate($token);
if ($errors) {
$this->throwApiProblemValidationException($errors);
}
... lines 30 - 33
}
... lines 35 - 36

Great, so this actually should all be setup. Of course what I forgot to do was write the scenario first, shame on me! Let's write the scenario to make sure this is in full operating order.

I'll copy most of the working version. Here, we won't pass any request body. Fortunately we've made our decode function able to handle that. We know the status code is going to be 400. We can check to see that the errors.notes property will equal the message that is on the ApiToken class. It will be this message right here:

38 lines features/api/token.feature
... lines 1 - 31
Scenario: Creating a token without a note
Given there is a user "weaverryan" with password "test"
And I authenticate with user "weaverryan" and password "test"
When I request "POST /api/tokens"
Then the response status code should be 400
And the "errors.notes" property should equal "Please add some notes about this token"

Alright!

This starts on line 33, so let's run just this scenario:

php vendor/bin/behat features/api/token.feature:33

Oh no, and it actually passes! Instead of the 400 we want, it is giving us the 201, which means that things are not failing validation. You can see for the note it says default note. If you look back in our TokenController... Ah ha! It's because I forgot to take off the default value. So now it's either going to be set to whatever the note is or null:

36 lines src/KnpU/CodeBattle/Controller/Api/TokenController.php
... lines 1 - 16
public function newAction(Request $request)
{
... lines 19 - 20
$data = $this->decodeRequestBodyIntoParameters($request);
... lines 22 - 23
$token->notes = $data->get('notes');
... lines 25 - 33
}
... lines 35 - 36

And if it's set to null we should see our validation kick in. And we do, perfect!

Leave a comment!