131 lines
3.3 KiB
Rust
131 lines
3.3 KiB
Rust
use axum::{
|
|
Json,
|
|
extract::{Query, State},
|
|
http::StatusCode,
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use pile_config::Label;
|
|
use pile_value::{extract::traits::ExtractState, value::PileValue};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{collections::HashMap, sync::Arc};
|
|
use utoipa::IntoParams;
|
|
|
|
use crate::Datasets;
|
|
|
|
#[derive(Deserialize, IntoParams)]
|
|
pub struct SchemaQuery {
|
|
source: String,
|
|
key: String,
|
|
|
|
#[serde(default)]
|
|
hidden: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum ApiValue {
|
|
Binary { binary: bool, mime: String },
|
|
Object { object: bool },
|
|
Array(Vec<ApiValue>),
|
|
String(String),
|
|
Number(serde_json::Number),
|
|
Null,
|
|
}
|
|
|
|
pub type SchemaResponse = HashMap<String, ApiValue>;
|
|
|
|
async fn pile_value_to_api(
|
|
state: &ExtractState,
|
|
value: PileValue,
|
|
) -> Result<ApiValue, std::io::Error> {
|
|
match value {
|
|
PileValue::String(s) => Ok(ApiValue::String(s.to_string())),
|
|
PileValue::U64(n) => Ok(ApiValue::Number(n.into())),
|
|
PileValue::I64(n) => Ok(ApiValue::Number(n.into())),
|
|
PileValue::Null => Ok(ApiValue::Null),
|
|
|
|
PileValue::Binary(x) => Ok(ApiValue::Binary {
|
|
binary: true,
|
|
mime: x.mime().to_string(),
|
|
}),
|
|
|
|
PileValue::Array(arr) => {
|
|
let mut out = Vec::with_capacity(arr.len());
|
|
for item in arr.iter() {
|
|
out.push(Box::pin(pile_value_to_api(state, item.clone())).await?);
|
|
}
|
|
Ok(ApiValue::Array(out))
|
|
}
|
|
|
|
PileValue::ObjectExtractor(_) | PileValue::ListExtractor(_) | PileValue::Item(_) => {
|
|
Ok(ApiValue::Object { object: true })
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get all schema field values for a single item.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/schema",
|
|
params(
|
|
("source" = String, Query, description = "Source label"),
|
|
("key" = String, Query, description = "Item key"),
|
|
("hidden" = bool, Query, description = "Include hidden fields (default: false)"),
|
|
),
|
|
responses(
|
|
(status = 200, description = "Schema field values as a map of label to value"),
|
|
(status = 400, description = "Invalid source label"),
|
|
(status = 404, description = "Item not found"),
|
|
(status = 500, description = "Internal server error"),
|
|
)
|
|
)]
|
|
pub async fn schema_all(
|
|
State(state): State<Arc<Datasets>>,
|
|
Query(params): Query<SchemaQuery>,
|
|
) -> Response {
|
|
let label = match Label::try_from(params.source.clone()) {
|
|
Ok(l) => l,
|
|
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 extract_state = ExtractState { ignore_mime: false };
|
|
let item = PileValue::Item(item);
|
|
|
|
let mut result: SchemaResponse = HashMap::new();
|
|
|
|
for (field_label, field_spec) in &state.config.schema {
|
|
if field_spec.hidden && !params.hidden {
|
|
continue;
|
|
}
|
|
|
|
let mut value = None;
|
|
for path in &field_spec.path {
|
|
match item.query(&extract_state, path).await {
|
|
Ok(Some(PileValue::Null)) | Ok(None) => continue,
|
|
Ok(Some(v)) => {
|
|
value = Some(v);
|
|
break;
|
|
}
|
|
Err(e) => {
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")).into_response();
|
|
}
|
|
}
|
|
}
|
|
|
|
let Some(v) = value else { continue };
|
|
|
|
let api_value = match pile_value_to_api(&extract_state, v).await {
|
|
Ok(v) => v,
|
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")).into_response(),
|
|
};
|
|
|
|
result.insert(field_label.as_str().to_owned(), api_value);
|
|
}
|
|
|
|
(StatusCode::OK, Json(result)).into_response()
|
|
}
|