1use std::sync::Arc;
2
3use axum::extract::{Query, State};
4use axum::http::StatusCode;
5use axum::response::{IntoResponse, Response};
6use axum::routing::get;
7use axum::{Json, Router};
8use serde::Deserialize;
9use tracing::instrument;
10
11use crate::exceptions::AppError;
12use crate::models::RequestData;
13use crate::services::{HttpClient, fetch_result};
14
15#[derive(Clone)]
16pub struct ApiState {
17 pub client: Arc<HttpClient>,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct FetchPayload {
22 pub exam: String,
23 pub year: String,
24 pub board: String,
25 pub roll: String,
26 pub reg: String,
27}
28
29impl From<FetchPayload> for RequestData {
30 fn from(value: FetchPayload) -> Self {
31 Self {
32 exam: value.exam,
33 year: value.year,
34 board: value.board,
35 roll: value.roll,
36 reg: value.reg,
37 }
38 }
39}
40
41pub fn router(state: ApiState) -> Router {
42 Router::new()
43 .route("/fetch", get(fetch_get).post(fetch_post))
44 .with_state(state)
45}
46
47#[instrument(skip(state))]
48async fn fetch_get(State(state): State<ApiState>, Query(payload): Query<FetchPayload>) -> Response {
49 handle_fetch(state, payload).await
50}
51
52#[instrument(skip(state))]
53async fn fetch_post(State(state): State<ApiState>, Json(payload): Json<FetchPayload>) -> Response {
54 handle_fetch(state, payload).await
55}
56
57fn map_error(err: AppError) -> Response {
58 match err {
59 AppError::Network(msg) => (StatusCode::BAD_GATEWAY, msg).into_response(),
60 AppError::Parse(msg) => (StatusCode::BAD_GATEWAY, msg).into_response(),
61 AppError::Captcha(msg) => (StatusCode::BAD_REQUEST, msg).into_response(),
62 }
63}
64
65#[instrument(skip(state), fields(exam = %payload.exam, year = %payload.year, board = %payload.board, roll = %payload.roll))]
66async fn handle_fetch(state: ApiState, payload: FetchPayload) -> Response {
67 let request: RequestData = payload.into();
68 match fetch_result(state.client.as_ref(), &request).await {
69 Ok(result) => Json(result).into_response(),
70 Err(err) => map_error(err),
71 }
72}