упр.12 задача 1

Предадени решения

Краен срок:
14.01.2026 23:59
Точки:
4

Срокът за предаване на решения е отминал

include! { "../src/lib.rs" }
#[test]
fn test_hello() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/hello",
body: None,
});
assert_eq!(response, Response::ok("Hello, world".to_string()));
}
#[test]
fn test_square() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/square/11",
body: None,
});
assert_eq!(response, Response::ok("121".to_string()));
}
#[test]
fn test_missing_arg() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/square",
body: None,
});
assert_eq!(response, Response::bad_request());
}
#[test]
fn test_not_found() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/no_such_path",
body: None,
});
assert_eq!(response, Response::not_found());
}

Преди да започнете задачата, е добре да си припомните какво са макроси, като тук има съкратена информация за тях: Обобщена информация

Да се реализира макрос api_routes!, който генерира логика за маршрутизация (routing) на HTTP заявки. Макросът трябва да превръща декларативно описание на пътищата във функция pub fn route(req: Request) -> Response.

Изисквания:

  1. Статични пътища: При съвпадение на метод и точен път (напр. /hello), да се извиква съответната функция.
  2. Динамични пътища: При пътища с параметър (напр. /square/:x), макросът трябва да:
    • Разпознава префикса (всичко преди :).
    • Извлича стойността на параметъра от URL-а.
    • Опитва да парсне параметъра към посочения тип (напр. i32).
    • При неуспешно парсване да връща 400 Bad Request.
  3. Обработка на грешки:
    • Резултатът от handler функциите (Ok или Err) трябва да се преобразува в съответния Response.
    • Ако никой маршрут не съвпадне, да се връща 404 Not Found.
// =========================================================
// ДАДЕНИ ТИПОВЕ (НЕ СЕ ПРОМЕНЯТ)
// =========================================================

#[derive(Debug)]
pub struct Request<'a> {
    pub method: &'a str,
    pub path: &'a str,
    pub body: Option<&'a str>,
}

#[derive(Debug, PartialEq)]
pub struct Response {
    pub status: u16,
    pub body: String,
}

impl Response {
    pub fn ok(body: String) -> Self {
        Self { status: 200, body }
    }

    pub fn bad_request() -> Self {
        Self { status: 400, body: String::new() }
    }

    pub fn not_found() -> Self {
        Self { status: 404, body: String::new() }
    }
}

#[derive(Debug)]
pub enum ApiError {
    BadRequest,
    NotFound,
}

// =========================================================
// TODO: МАКРОС api_routes!
// Имплементирайте генерирането на функцията route()
// =========================================================

macro_rules! api_routes {
    (
        $(
            $method:ident $handler:ident $( ( $param_name:ident : $param_ty:ty ) )?;
        )*
    ) => {
        pub fn route(req: Request) -> Response {
            $(
                // Съвет: stringify!($method) ще ви даде името на метода като низ
                if req.method == stringify!($method) && ... {
                    // Handle
                    // req.method == $method
                    // req.path == /$handler/...
                    todo!()
                }
            )*

            Response::not_found()
        }
    };
}

// =========================================================
// HANDLER ФУНКЦИИ (ПОПЪЛВАТ СЕ ОТ СТУДЕНТА)
// =========================================================

fn hello() -> Result<String, ApiError> {
    // Върнете "Hello, world!"
    todo!("Имплементирайте hello")
}

fn square(x: i32) -> Result<String, ApiError> {
    // Върнете квадрата на x като низ
    todo!("Имплементирайте square")
}

// =========================================================
// ИЗВИКВАНЕ НА МАКРОСА (НЕ СЕ ПРОМЕНЯ)
// =========================================================

api_routes! {
    GET hello;
    GET square(x : i32);
}
input Макросът прави това output
GET /hello извикай функцията hello() 200 OK: "Hello world!"
GET /square/5 вземи "5", направи го на число и извикай square(5) 200 OK: 25
GET /square/abc взима "abc", вижда, че не е число и връща грешка 400 Bad Request
GET /something не открива нито един маршрут и връща грешка 404 Not Found

Хитринка: когато искате да разграничите различни ситуации в имплементацията на едно макро, можете да изполвате помощно макро.
(За използване от външна библиотека помощните макрота трябва да са публично видими (export-нати), но по конвенция името започва с долни черти, за да означи, че това е имплементационен детайл.)

macro_rules! __helper {
    ( /* some tokens */ ) => { /* ... */ };
    ( /* other tokens */ ) => { /* ... */ };
}

Друг вариант е да използвате private ръкави на същото macro.
По конвенция, ако ръкав започва с @some_identifier, той е предназначен за вътрешно ползване от самото макро.

macro_rules! api_routes {
    // публични ръкави
    ( $( $method:ident $handler:ident $( ( $param_name:ident : $param_ty:ty ) )? )* ) => {
        // имплементация...
        // по някое време се изиква api_routes!(@handle_route ....) 
    };

    // private ръкави
    (@handle_route /* some tokens */ ) => { /* ... */ };
    (@handle_route /* other tokens */ ) => { /* ... */ };
}

Задължително прочетете (или си припомнете): Указания за предаване на домашни

Погрижете се решението ви да се компилира с базовия тест:

include! { "../src/lib.rs" }
#[test]
fn test_hello() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/hello",
body: None,
});
assert_eq!(response, Response::ok("Hello, world".to_string()));
}
#[test]
fn test_square() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/square/11",
body: None,
});
assert_eq!(response, Response::ok("121".to_string()));
}
#[test]
fn test_missing_arg() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/square",
body: None,
});
assert_eq!(response, Response::bad_request());
}
#[test]
fn test_not_found() {
api_routes! {
GET hello;
GET square(x : i32);
}
let response = route(Request {
method: "GET",
path: "/no_such_path",
body: None,
});
assert_eq!(response, Response::not_found());
}