feat: implement custom deserializer for path segments with format
extensionepisode-actions
parent
73928e78f4
commit
3c4af12fa1
|
@ -6,11 +6,12 @@ use axum::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{self, User},
|
db,
|
||||||
server::{
|
server::{
|
||||||
error::{AppError, AppResult},
|
error::{AppError, AppResult},
|
||||||
gpodder::{
|
gpodder::{
|
||||||
auth_middleware,
|
auth_middleware,
|
||||||
|
format::{Format, StringWithFormat},
|
||||||
models::{Device, DevicePatch, DeviceType},
|
models::{Device, DevicePatch, DeviceType},
|
||||||
},
|
},
|
||||||
Context,
|
Context,
|
||||||
|
@ -26,14 +27,14 @@ pub fn router(ctx: Context) -> Router<Context> {
|
||||||
|
|
||||||
async fn get_devices(
|
async fn get_devices(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path(username): Path<String>,
|
Path(username): Path<StringWithFormat>,
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<db::User>,
|
||||||
) -> AppResult<Json<Vec<Device>>> {
|
) -> AppResult<Json<Vec<Device>>> {
|
||||||
// Check suffix is present and return 404 otherwise; axum doesn't support matching part of a
|
if username.format != Format::Json {
|
||||||
// route segment
|
return Err(AppError::NotFound);
|
||||||
let username = username.strip_suffix(".json").ok_or(AppError::NotFound)?;
|
}
|
||||||
|
|
||||||
if username != user.username {
|
if *username != user.username {
|
||||||
return Err(AppError::BadRequest);
|
return Err(AppError::BadRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +56,13 @@ async fn get_devices(
|
||||||
|
|
||||||
async fn post_device(
|
async fn post_device(
|
||||||
State(ctx): State<Context>,
|
State(ctx): State<Context>,
|
||||||
Path((_username, id)): Path<(String, String)>,
|
Path((_username, id)): Path<(String, StringWithFormat)>,
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<db::User>,
|
||||||
Json(patch): Json<DevicePatch>,
|
Json(patch): Json<DevicePatch>,
|
||||||
) -> AppResult<()> {
|
) -> AppResult<()> {
|
||||||
let id = id
|
if id.format != Format::Json {
|
||||||
.strip_suffix(".json")
|
return Err(AppError::NotFound);
|
||||||
.ok_or(AppError::NotFound)?
|
}
|
||||||
.to_string();
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
if let Some(mut device) = db::Device::by_device_id(&ctx.pool, user.id, &id)? {
|
if let Some(mut device) = db::Device::by_device_id(&ctx.pool, user.id, &id)? {
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use serde::{
|
||||||
|
de::{value::StrDeserializer, Visitor},
|
||||||
|
Deserialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Format {
|
||||||
|
Json,
|
||||||
|
OPML,
|
||||||
|
Plaintext,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StringWithFormat {
|
||||||
|
pub s: String,
|
||||||
|
pub format: Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for StringWithFormat {
|
||||||
|
type Target = String;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for StringWithFormat {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct StrVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for StrVisitor {
|
||||||
|
type Value = StringWithFormat;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str(
|
||||||
|
"`text.ext` format, with `ext` being one of `json`, `opml` or `plaintext`",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
if let Some((text, ext)) = v.rsplit_once('.') {
|
||||||
|
let format = Format::deserialize(StrDeserializer::new(ext))?;
|
||||||
|
|
||||||
|
Ok(StringWithFormat {
|
||||||
|
s: text.to_string(),
|
||||||
|
format,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(E::custom(format!("invalid format '{}'", v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(StrVisitor)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod advanced;
|
mod advanced;
|
||||||
|
mod format;
|
||||||
mod models;
|
mod models;
|
||||||
mod simple;
|
mod simple;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue