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 the minimum_lessons_remaining() and the passed_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 a Vec<Student>. Add a new method that creates a new DrivingSchool 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 next n days.

How did you organize your code? Did you create a separate file for the Student and DrivingSchool structs? Did you create a models 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 and sleep from the tokio crate? Note that you cannot use the std::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 and owner? The instructor can only read the students and the owner 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?