Я работаю над приложением на основе Axum на Rust, где один из моих обработчиков получает полезную нагрузку JSON, которая десериализуется в структуру MyRequest
. Структура содержит поле my_enum
типа MyEnum
, которое представляет собой перечисление с вариантами Foo
и Bar
.
Вот упрощенная версия моего кода:
use axum::{extract::Json, response::IntoResponse};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum MyEnum {
Foo,
Bar,
}
#[derive(Deserialize)]
struct MyRequest {
my_enum: MyEnum,
// other fields...
}
async fn my_handler(
payload: Result<Json<MyRequest>, axum::extract::rejection::JsonRejection>,
) -> impl IntoResponse {
match payload {
Ok(Json(request)) => {
// Handle the request...
}
Err(e) => {
// Handle the error...
// Right now, I'm just checking the error message.
if e.to_string().contains("my_enum") {
// Specific handling for enum deserialization error
} else {
// Generic error handling
}
}
}
}
В настоящее время, если входящий JSON содержит недопустимое значение для my_enum
, Serde не может его десериализовать, и Axum возвращает JsonRejection
. Чтобы отличить ошибки, вызванные недопустимым значением my_enum
, от других потенциальных ошибок, я проверяю сообщение об ошибке с помощью проверки строки, например e.to_string().contains("my_enum")
.
Этот подход кажется хрупким, поскольку он опирается на конкретную формулировку сообщения об ошибке, которая может измениться и не гарантируется ее согласованность.
Есть ли способ настроить Serde так, чтобы он выдавал конкретную ошибку (или более надежно идентифицировал сбой десериализации), когда значение, присвоенное полю my_enum
в MyRequest
, не может быть десериализовано? В идеале я хотел бы справиться с этим сценарием, не прибегая к хрупкому сопоставлению строк.
В настоящее время я использую стандартный шаблон Axum для обработки десериализации:
payload: Result<Json<MyRequest>, axum::extract::rejection::JsonRejection>
А затем проверяем ошибку следующим образом:
if e.to_string().contains("my_enum") {
// Specific handling for enum deserialization error
}
Однако, как уже упоминалось, этот подход не идеален из-за его зависимости от содержимого сообщения об ошибке.
Я ищу более надежное решение, которое позволит мне надежно определять случаи сбоя десериализации my_enum
, предпочтительно за счет использования возможностей Serde или настройки процесса десериализации.
@ĐorđeZeljić не уверен. Я думаю, моя проблема в том, что я получаю JsonRejection::JsonDataError
, но я не могу четко сказать, связано ли это с тем или иным полем, если только я не проверю сообщение, и мне не нравится писать бизнес-логику, основанную на магических строках.
@ĐorđeZeljić, вероятно, собственный экстрактор - самый элегантный подход, но я до сих пор не вижу способа узнать, возникает ли ошибка при десериализации определенного поля.
Может быть, вы можете установить это поле Option<MyEnum>
, а затем проверить Some
/None
?
Если вы проверите первую ссылку @ĐorđeZeljić, вы увидите, что axum использует serde_path_to_error::Error
, у которого есть метод path. Вы можете использовать его, чтобы проверить сегменты, в которых именно произошла ошибка, и сопоставить их с возможными полями вашей структуры.
Вам нужно выполнить двухэтапную десериализацию. Создайте промежуточный Request
, в котором вы десериализуете свои специальные поля в serde_json::Value
, а затем десериализуете это значение в свою структуру. Например:
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Data {
Foo,
Bar,
}
struct Request {
data: Data,
field: i32,
}
#[derive(Deserialize)]
struct RequestIntermediate {
data: serde_json::Value,
field: i32,
}
enum APIError {
RequestError(serde_json::Error),
DataError(serde_json::Error),
}
fn deserialize_request(input: &[u8]) -> Result<Request, APIError> {
let RequestIntermediate { data, field }: RequestIntermediate =
serde_json::from_slice(input).map_err(APIError::RequestError)?;
let data: Data = serde_json::from_value(data).map_err(APIError::DataError)?;
Ok(Request { data, field })
}
Если вы хотите автоматизировать извлечение этого запроса, вы можете реализовать FromRequest
. Вот краткий пример (без правильной обработки ошибок).
struct RequestExtractor(pub Result<Request, APIError>);
#[axum::async_trait]
impl<S: Send + Sync> FromRequest<S> for RequestExtractor {
// You should probably also use some other rejection.
type Rejection = BytesRejection;
async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
// Ignoring Content-type. You might want to add this check.
// See axum's implementation of FromRequest for axum::Json.
let bytes = Bytes::from_request(req, state).await?;
let request = deserialize_request(bytes.as_ref());
Ok(RequestExtractor(request))
}
}
async fn handler(RequestExtractor(payload): RequestExtractor) {
match payload {
Ok(pyload) => todo!(),
Err(APIError::RequestError(_)) => todo!(),
Err(APIError::DataError(_)) => todo!(),
}
}
Вы ищете «Настройка ответа экстрактора» docs.rs/axum/latest/axum/extract/… ?