Skip to content
Clone
194c44c
188 lines 5.6 KB Rust
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::{get, post};
use axum::{Json, Router};
use export::rest::{AppResponseData, NoData};
use serde::Deserialize;
use jwt::{Session, SessionUser};
use utoipa::{IntoParams, OpenApi, ToSchema};
use uuid::Uuid;
use ::search::{SearchError, SearchPage, SearchQuery, SearchService};
#[derive(Debug, Clone, Deserialize, IntoParams)]
pub struct SymbolQuery {
pub q: String,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct ReindexCommand {
pub repo_id: Option<Uuid>,
#[serde(default)]
pub force_full: bool,
}
#[derive(Debug)]
pub struct SearchApiError(SearchError);
impl From<SearchError> for SearchApiError {
fn from(value: SearchError) -> Self {
Self(value)
}
}
impl IntoResponse for SearchApiError {
fn into_response(self) -> Response {
let (status, message, code) = match &self.0 {
SearchError::Disabled => (
StatusCode::SERVICE_UNAVAILABLE,
"search is unavailable",
"search_disabled",
),
SearchError::Invalid(_) => (
StatusCode::BAD_REQUEST,
"invalid search request",
"invalid_search_query",
),
SearchError::Task { .. } | SearchError::Remote { .. } | SearchError::TimedOut => (
StatusCode::SERVICE_UNAVAILABLE,
"search is temporarily unavailable",
"search_unavailable",
),
_ => (
StatusCode::INTERNAL_SERVER_ERROR,
"search request failed",
"search_failed",
),
};
(
status,
AppResponseData::<NoData>::err(status.as_u16() as i32, message)
.with_error(self.0.to_string())
.with_error_code(code),
)
.into_response()
}
}
#[utoipa::path(
get,
path = "/search",
tag = "search",
description = "全局检索当前调用者可见的代码、符号、议题、合并请求与聊天消息。",
params(SearchQuery),
responses((status = 200, description = "search results", body = AppResponseData<SearchPage>))
)]
pub async fn global_search(
State(service): State<SearchService>,
session: Session,
Query(query): Query<SearchQuery>,
) -> Result<AppResponseData<SearchPage>, SearchApiError> {
Ok(AppResponseData::ok(
service.search(session.uid(), query).await?,
))
}
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/symbols",
tag = "search",
description = "检索仓库默认分支中已索引的符号。",
params(git::handler::RepoPath, SymbolQuery),
responses((status = 200, description = "symbol results", body = AppResponseData<SearchPage>))
)]
pub async fn repo_symbols(
State(service): State<SearchService>,
session: Session,
Path(path): Path<git::handler::RepoPath>,
Query(query): Query<SymbolQuery>,
) -> Result<AppResponseData<SearchPage>, SearchApiError> {
Ok(AppResponseData::ok(
service
.symbols(
session.uid(),
path.space_name,
path.repo_name,
query.q,
query.limit,
query.offset,
)
.await?,
))
}
#[utoipa::path(
get,
path = "/repos/{space_name}/{repo_name}/symbols/{symbol_name}/references",
tag = "search",
description = "返回仓库内同名符号的词法引用与定义候选。",
params(git::handler::RepoPath, SymbolQuery),
responses((status = 200, description = "symbol references", body = AppResponseData<SearchPage>))
)]
pub async fn symbol_references(
State(service): State<SearchService>,
session: Session,
Path((space_name, repo_name, symbol_name)): Path<(String, String, String)>,
Query(query): Query<SymbolQuery>,
) -> Result<AppResponseData<SearchPage>, SearchApiError> {
let name = if query.q.trim().is_empty() {
symbol_name
} else {
query.q
};
Ok(AppResponseData::ok(
service
.references(
session.uid(),
space_name,
repo_name,
name,
query.limit,
query.offset,
)
.await?,
))
}
pub async fn reindex(
State(service): State<SearchService>,
Json(command): Json<ReindexCommand>,
) -> Result<AppResponseData<NoData>, SearchApiError> {
if command.force_full {
::search::outbox::clear_repo_state(service.database().write(), command.repo_id)
.await
.map_err(SearchError::from)?;
}
if let Some(repo_id) = command.repo_id {
::search::outbox::enqueue_repo(service.database().write(), repo_id)
.await
.map_err(SearchError::from)?;
} else {
service.queue_all().await?;
}
Ok(AppResponseData::empty())
}
pub fn router() -> Router<SearchService> {
Router::new()
.route("/search", get(global_search))
.route("/repos/{space_name}/{repo_name}/symbols", get(repo_symbols))
.route(
"/repos/{space_name}/{repo_name}/symbols/{symbol_name}/references",
get(symbol_references),
)
}
pub fn internal_router() -> Router<SearchService> {
Router::new().route("/search/reindex", post(reindex))
}
#[derive(OpenApi)]
#[openapi(
paths(global_search, repo_symbols, symbol_references),
components(schemas(SearchPage, ::search::SearchHit, ::search::SearchKind))
)]
pub struct SearchApiDoc;