warehouse/src/search.rs

96 lines
2.2 KiB
Rust

use std::sync::Arc;
use async_trait::async_trait;
use axum::{
extract::{FromRequest, Query as AxumQuery, RequestParts},
Extension, Json,
};
use semver::Version;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use sqlx::query_as;
use crate::{db::PgU32, db_error, Errors, RespResult, State};
pub struct Query<T>(T);
#[async_trait]
impl<T, B> FromRequest<B> for Query<T>
where
T: DeserializeOwned,
B: Send,
{
type Rejection = Errors;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let res = AxumQuery::<T>::from_request(req).await;
match res {
Ok(AxumQuery(v)) => Ok(Query(v)),
Err(e) => Err(Errors::new(format_args!("Invalid query options: {e}"))),
}
}
}
#[derive(Deserialize)]
pub struct SearchQueryParams {
q: String,
per_page: Option<u8>,
}
#[derive(Serialize)]
pub struct SearchResponse {
crates: Vec<Crate>,
meta: Meta,
}
#[derive(Serialize)]
pub struct Crate {
name: String,
max_version: Version,
description: String,
}
#[derive(Serialize)]
pub struct Meta {
total: u32,
}
pub async fn search(
Query(params): Query<SearchQueryParams>,
Extension(state): Extension<Arc<State>>,
) -> RespResult<Json<SearchResponse>> {
let crates = query_as::<_, (PgU32, String)>(
"SELECT id, name FROM crates ORDER BY SIMILARITY(name, $1) DESC LIMIT $2;",
)
.bind(&params.q)
.bind(i16::from(params.per_page.unwrap_or(10)))
.fetch_all(&state.db)
.await
.map_err(db_error)?;
let mut crates2 = Vec::new();
for craet in crates {
let v: Option<(String, Option<String>)> = query_as(
"SELECT vers, description FROM versions WHERE crate_id = $1 ORDER BY vers DESC LIMIT 1",
)
.bind(craet.0)
.fetch_optional(&state.db)
.await
.map_err(db_error)?;
if let Some((vers, desc)) = v {
crates2.push(Crate {
name: craet.1,
max_version: vers.parse().unwrap(),
description: desc.unwrap_or_else(|| "".to_owned()),
});
}
}
Ok(Json(SearchResponse {
crates: crates2,
// TODO: total
meta: Meta { total: 0 },
}))
}