91 lines
2.4 KiB
Rust
91 lines
2.4 KiB
Rust
use axum::{
|
|
Json,
|
|
extract::{Query, State},
|
|
http::{StatusCode, header},
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use pile_config::{Label, objectpath::ObjectPath};
|
|
use serde::Deserialize;
|
|
use std::sync::Arc;
|
|
use tracing::debug;
|
|
use utoipa::ToSchema;
|
|
|
|
use crate::{Datasets, PileValue, extract::MetaExtractor};
|
|
|
|
#[derive(Deserialize, ToSchema)]
|
|
pub struct FieldQuery {
|
|
source: String,
|
|
key: String,
|
|
path: String,
|
|
}
|
|
|
|
/// Extract a specific field from an item's metadata
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/field",
|
|
params(
|
|
("source" = String, Query, description = "Source label"),
|
|
("key" = String, Query, description = "Item key"),
|
|
("path" = String, Query, description = "Object path (e.g. $.flac.title)"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Field value as JSON"),
|
|
(status = 400, description = "Invalid source label or path"),
|
|
(status = 404, description = "Item or field not found"),
|
|
(status = 500, description = "Internal server error"),
|
|
)
|
|
)]
|
|
pub async fn get_field(
|
|
State(state): State<Arc<Datasets>>,
|
|
Query(params): Query<FieldQuery>,
|
|
) -> Response {
|
|
debug!(
|
|
message = "Serving /field",
|
|
source = params.source,
|
|
key = params.key,
|
|
path = params.path,
|
|
);
|
|
|
|
let label = match Label::try_from(params.source) {
|
|
Ok(l) => l,
|
|
Err(e) => return (StatusCode::BAD_REQUEST, format!("{e:?}")).into_response(),
|
|
};
|
|
|
|
let path: ObjectPath = match params.path.parse() {
|
|
Ok(p) => p,
|
|
Err(e) => return (StatusCode::BAD_REQUEST, format!("{e:?}")).into_response(),
|
|
};
|
|
|
|
let Some(item) = state.get(&label, ¶ms.key).await else {
|
|
return StatusCode::NOT_FOUND.into_response();
|
|
};
|
|
|
|
let extractor = MetaExtractor::new(&item);
|
|
let root: PileValue<'_> = PileValue::Extractor(Arc::new(extractor));
|
|
|
|
let value = match root.query(&path).await {
|
|
Ok(Some(v)) => v,
|
|
Ok(None) => return StatusCode::NOT_FOUND.into_response(),
|
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")).into_response(),
|
|
};
|
|
|
|
match value {
|
|
PileValue::String(s) => (
|
|
StatusCode::OK,
|
|
[(header::CONTENT_TYPE, "text/plain")],
|
|
s.to_string(),
|
|
)
|
|
.into_response(),
|
|
PileValue::Blob { mime, bytes } => (
|
|
StatusCode::OK,
|
|
[(header::CONTENT_TYPE, mime.to_string())],
|
|
bytes.as_ref().clone(),
|
|
)
|
|
.into_response(),
|
|
_ => match value.to_json().await {
|
|
Ok(json) => (StatusCode::OK, Json(json)).into_response(),
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")).into_response(),
|
|
},
|
|
}
|
|
}
|