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>, Query(params): Query, ) -> 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(), }, } }