Exercise - Driving School
We're going to help a local driving school with their student administration.
Start by creating a new project driving_school
and add the chrono
crate to your Cargo.toml
.
Exercise 1: Students; after completing Session 2
Create a Student
struct with the following fields:
name: String
date_of_birth: chrono::NaiveDate
has_id: bool
passed_eye_test: bool
lessons_completed: u16
car_type: CarType
exam_date: Option<chrono::NaiveDate>
passed_exam: bool
The CarType
enum should have the following variants:
Manual
Automatic
Learning to drive a manual car is more difficult than learning to drive an automatic car. A student needs 5 extra lessons to learn to drive a manual car. The minimum number of lessons to learn to drive an automatic car is 20.
Use the builder pattern to create a Student
instance. The Student
struct should have a new
method that takes the
mandatory fields name: String
, date_of_birth: chrono::NaiveDate
, has_id: bool
and passed_eye_test: bool
.
Then add a method with_car_type(mut self, car_type: CarType) -> Self
that sets the car_type
field.
In this particular country, students can only start with their driving lessons when they will turn 17 in the next 6 months. They also need to have an ID and have doctor's proof that they passed an eye test.
To implement this, create a method can_start_driving_lessons(&self) -> bool
that returns true
if the student can
start driving lessons. I'd suggest creating a helper function is_seventeen_in_six_months(&self) -> bool
that
returns true
if the student will turn 17 in the next 6 months. Maybe you can
use test-driven-development to implement this method.
The lessons_completed
field should be incremented by 1 when the student completes a lesson. Create a
method complete_lesson(&mut self)
that increments the lessons_completed
field.
Exercise 2: Student validations; after completing Session 3
Add the anyhow
crate to your Cargo.toml
.
Change the new
method to return an anyhow::Result<Self>
. The method should validate if the student's date of birth
is valid. You cannot sign up as a student if you're not at least 16 1/2 years old. If the date of birth is invalid, the
method should return an error.
Also add some other validations to the new
method:
- They must have a name.
- The student must have an ID.
You can quickly create an error with
anyhow!
like this:anyhow!("Student must be at least 16 1/2 years old")
.
We also need a minimum_lessons_remaining(&self) -> u8
method that returns the number of lessons remaining, depending
on the car type. Add a test that checks if the method returns the correct number of lessons remaining, for: 0, 1, 5, 20,
21, and 30 lessons.
Include the set_exam_date(&mut self, exam_date: NaiveDate) -> Result<()>
function. Think about the validations you
need to add to this method.
Finally, add the std::fmt::Display
trait to the Student
struct. The Display
implementation should return a string
with the student's name, type of car they're learning to drive, the number of lessons they've completed and the number
of lessons remaining. Use this format:
Name: Alice; Car type: Manual; Lessons completed: 5; Lessons remaining: 0
What validations did you add to the
set_exam_date
method? Did you check theminimum_lessons_remaining()
and thepassed_eye_test
field?
Exercise 3: Student lists; after completing Session 4
To help the driving school instructors, we'll include some functions to print a list of students. They are interested in:
- Students who still need to submit their eye test.
- Students who have completed all their lessons and are ready to take the exam.
- Students with an upcoming exam in the next 7 days.
Complete these steps:
-
Add a
DrivingSchool
struct that contains aVec<Student>
. Add anew
method that creates a newDrivingSchool
instance with an empty list of students. -
Add a method
add_student(&mut self, student: Student)
that adds a student to the list. -
Add a method
students_needing_eye_test(&self) -> Vec<&Student>
that returns a list of students that still need to hand in their eye test. -
Add a method
students_ready_for_exam(&self) -> Vec<&Student>
that returns a list of students who have completed all their lessons and are ready to take the exam. -
Add a method
students_with_upcoming_exam(&self, in_days: u8) -> Vec<&Student>
that returns a list of students who have an upcoming exam in the nextn
days.
How did you organize your code? Did you create a separate file for the
Student
andDrivingSchool
structs? Did you create amodels
module?
Have you added tests for the
DrivingSchool
operations? Did you remember to filter out the students who have already passed their exam? What about some test helpers to create students and initialize the driving school?
Exercise 4: Cleaning-up; after completing Session 5
Due to data privacy regulations, we need to remove all personal data of students who have passed their exam. Add a background task that runs every day at midnight to remove the personal information of students who have passed their exam.
Create a clean_up_students(&mut self)
method that removes the personal information of students who have passed their
exam.
Since the background task runs asynchronously, you must use the tokio
runtime. Add the tokio
crate to your
cargo.toml
. Make sure you include the full feature set: tokio = { version = "1", features = ["full"] }
.
You need to refactor your main function to use the tokio::main
macro. Since the clean_up_students
method is called
from an asynchronous context, you need to make sure that the DrivingSchool
struct is protected by a `RwLock'.
Did you find the
retain
method useful to remove students who have passed their exam?
Are you using the
RwLock
andsleep
from thetokio
crate? Note that you cannot use thestd::thread::sleep
function, because it blocks the thread.
How did you keep the `main' function alive?
Exercise 5: REST API; after completing Session 6
The driving school wants to create a REST API to manage their students. They want to have the following endpoints:
GET /students
: Returns a list of all students.GET /students/{id}
: Returns a single student.GET /students?pending_exam=true&days=7
: Returns a list of students who have an upcoming exam in the next 7 days.GET /students?ready_for_exam=true
: Returns a list of students who are ready to take the exam.GET /students?eye_test=false
: Returns a list of students who still need to submit their eye test.POST /students
: Adds a new student.PUT /students/{id}/exam_date
: Updates the exam date of a student.DELETE /students/{id}
: Deletes a student.
To control access create a login
endpoint with basic authentication that returns a JWT token. You can use the
jsonwebtoken
crate to create and validate the JWT token. Make sure all endpoints are protected with the JWT token.
Can you think of a way to create two roles:
instructor
andowner
? Theinstructor
can only read the students and theowner
can add, update and delete students.
Is it OK to store the role in the JWT token? How can you validate the role?
Did you add logging to the REST API?