This endpoint is different than the GET
ones because there is a request body that we receive from the client that we need to decode and turn into a Movie.
We have built an ADT that represents all possible validation errors for a NewMovieRequest
.
If the name
is empty, return a MovieNameTooShort
and if the synopsis
is empty, return a MovieSynopsisTooShort
.
We have two different models here. A NewMovieRequest
represents a request that has been successfully decoded containing a name
and synopsis
to save into the database.
However, at the point of decoding, we do not know whether the name
and synopsis
obey our business rules.
We need to validate this model and if it is valid, we create a different type ValidatedMovie
that represents this. The two types contain the same information, but by making them two distinct types, we can enforce additional type safety and better readability.
Build a validate
function that takes a NewMovieRequest
and returns either an Invalid[NonEmptyList[MovieValidationError]]
or a Valid[ValidatedMovie]
.
Remember what you learned from ValidationExercises
.
Complete exercise
Run unit test: NewMovieValidatorSpec
We can see SaveMovieService
has a saveMovie
function taken in as a dependency. It is of type ValidatedMovie => IO[MovieId]
.
The save
function accepts a NewMovieRequest
and returns a IO[ValidatedNel[MovieValidationError, MovieId]]
. We want to validate the request, if it is valid, we save the movie and return the MovieId
, otherwise we return all the errors.
Complete exercise
Run unit test: SaveMovieServiceSpec
The Controller
is a little different this time. We have the entire request as an argument to the function. We want to decode the request into a NewMovieRequest
and then pass that into the saveNewMovie
function in the class constructor.
After that, we want to attempt
as usual and handle each possibility.
You will need to complete the following in order to get the Controller
to compile:
Decoder[NewMovieRequest]
Encoder[MovieValidationError]
Encoder[MovieId]
We need to describe how to convert from a Json
into NewMovieRequest
.
Hint: The incoming JSON body is the same shape as our NewMovieRequest
case class.
{
"name": "Titanic",
"synopsis": "A movie about ships"
}
Complete exercise
Implement the show
function first. This is a way to go from our type into a String
.
Complete exercise
Run unit test: MovieValidationErrorSpec
Next, create an Encoder
instance to convert our type into Json
.
Complete exercise
We also need a way to convert a MovieId
into Json
.
Complete exercise
Complete exercise
Run unit test: SaveMovieControllerSpec
Now let's wire the Service
and Controller
up in AppRuntime
.
Pass the newly instantiated saveMovieController
into AppRoutes
.
Change AppRoutes to accept a SaveMovieController
and then call saveMovie
given the req
uest!
Note that req@POST...
means that req
is an alias for the value on the right hand side.
Start the app using ./auto/start-local
and test it out!
curl -H "Accept: application/json" -X POST -d "{\"name\": \"\", \"synopsis\": \"\"}" http://localhost:9200/movies
curl -H "Accept: application/json" -X POST -d "{\"name\": \"Space Jam\", \"synopsis\": \"A movie about basketball\"}" http://localhost:9200/movies
curl http://localhost:9200/movies/2