use axum::{ Json, extract::{Query, State}, http::{StatusCode, header}, response::{IntoResponse, Response}, }; use pile_config::{Label, objectpath::ObjectPath}; use pile_value::value::PileValue; use serde::Deserialize; use std::{sync::Arc, time::Instant}; use tracing::debug; use utoipa::ToSchema; use crate::Datasets; #[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 { let start = Instant::now(); debug!( message = "Serving /field", source = params.source, key = params.key, path = params.path, ); let label = match Label::try_from(params.source.clone()) { 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 item = PileValue::Item(item); let value = match item.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(), }; debug!( message = "Served /field", source = params.source, key = params.key, path = params.path, time_ms = start.elapsed().as_millis() ); 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(), }, } }