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: Stringdate_of_birth: chrono::NaiveDatehas_id: boolpassed_eye_test: boollessons_completed: u16car_type: CarTypeexam_date: Option<chrono::NaiveDate>passed_exam: bool
The CarType enum should have the following variants:
ManualAutomatic
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_datemethod? Did you check theminimum_lessons_remaining()and thepassed_eye_testfield?
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
DrivingSchoolstruct that contains aVec<Student>. Add anewmethod that creates a newDrivingSchoolinstance 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 nextndays.
How did you organize your code? Did you create a separate file for the
StudentandDrivingSchoolstructs? Did you create amodelsmodule?
Have you added tests for the
DrivingSchooloperations? 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
retainmethod useful to remove students who have passed their exam?
Are you using the
RwLockandsleepfrom thetokiocrate? Note that you cannot use thestd::thread::sleepfunction, 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:
instructorandowner? Theinstructorcan only read the students and theownercan 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?