use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use axum::body::Bytes;
use axum::extract::State;
use axum::http::{HeaderMap, StatusCode};
use crate::bot::{DispatchOutcome, WebhookRunner};
use crate::{Error, Result};
pub const TELEGRAM_SECRET_HEADER: &str = "x-telegram-bot-api-secret-token";
const INVALID_SECRET_REASON: &str = "invalid webhook secret token";
const INVALID_JSON_REASON_PREFIX: &str = "failed to deserialize webhook update payload";
pub fn telegram_secret_token(headers: &HeaderMap) -> Option<&str> {
headers
.get(TELEGRAM_SECRET_HEADER)
.and_then(|value| value.to_str().ok())
}
pub async fn dispatch_webhook(
runner: &WebhookRunner,
headers: &HeaderMap,
payload: &[u8],
) -> Result<DispatchOutcome> {
let incoming_secret = telegram_secret_token(headers);
let update = runner
.parse_update_json(payload, incoming_secret)
.map_err(normalize_parse_error)?;
runner.dispatch_update_outcome(update).await
}
pub async fn dispatch_webhook_status(
runner: &WebhookRunner,
headers: &HeaderMap,
payload: &[u8],
) -> StatusCode {
match dispatch_webhook(runner, headers, payload).await {
Ok(_outcome) => StatusCode::OK,
Err(error) => status_from_error(&error),
}
}
pub fn webhook_handler(
State(runner): State<Arc<WebhookRunner>>,
headers: HeaderMap,
body: Bytes,
) -> Pin<Box<dyn Future<Output = StatusCode> + Send + 'static>> {
Box::pin(async move { dispatch_webhook_status(runner.as_ref(), &headers, body.as_ref()).await })
}
fn status_from_error(error: &Error) -> StatusCode {
match error {
Error::InvalidRequest { reason } if reason == INVALID_SECRET_REASON => {
StatusCode::UNAUTHORIZED
}
Error::InvalidRequest { reason } if reason.starts_with(INVALID_JSON_REASON_PREFIX) => {
StatusCode::BAD_REQUEST
}
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn normalize_parse_error(error: Error) -> Error {
match error {
Error::InvalidRequest { reason } if reason == "invalid webhook secret token" => {
Error::InvalidRequest {
reason: INVALID_SECRET_REASON.to_owned(),
}
}
Error::InvalidRequest { reason }
if reason.starts_with("failed to deserialize webhook update payload:") =>
{
Error::InvalidRequest {
reason: reason.replacen(
"failed to deserialize webhook update payload:",
INVALID_JSON_REASON_PREFIX,
1,
),
}
}
other => other,
}
}