Reformats import blocks, function signatures, and expression line wrapping in stemedb-api handlers, stemedb-core serde/source_record, and serde_helpers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
96 lines
3.2 KiB
Rust
96 lines
3.2 KiB
Rust
//! Handlers for subject and predicate discovery endpoints.
|
|
//!
|
|
//! These endpoints scan existing Redb indexes to expose the subjects
|
|
//! and predicates known to the system, enabling autocomplete/typeahead
|
|
//! in the dashboard.
|
|
|
|
use axum::{
|
|
extract::{Path, State},
|
|
Json,
|
|
};
|
|
use tracing::instrument;
|
|
|
|
use crate::{
|
|
dto::subjects::{ListPredicatesResponse, ListSubjectsParams, ListSubjectsResponse},
|
|
error::Result,
|
|
extractors::QsQuery,
|
|
state::AppState,
|
|
};
|
|
|
|
use stemedb_storage::{key_codec, KVStore};
|
|
|
|
/// List all known subjects, with optional prefix filtering.
|
|
///
|
|
/// Scans the `\x00SUBJECTS:` index in Redb. Supports prefix filtering
|
|
/// via the `q` parameter for typeahead/autocomplete use cases.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/v1/subjects",
|
|
params(
|
|
("q" = Option<String>, Query, description = "Prefix filter for subject names"),
|
|
("limit" = Option<usize>, Query, description = "Max results (default 100, max 1000)")
|
|
),
|
|
responses(
|
|
(status = 200, description = "List of subjects", body = ListSubjectsResponse),
|
|
(status = 500, description = "Internal server error", body = crate::dto::ErrorResponse)
|
|
),
|
|
tag = "discovery"
|
|
)]
|
|
#[instrument(skip(state), fields(q = ?params.q, limit = ?params.limit))]
|
|
pub async fn list_subjects(
|
|
State(state): State<AppState>,
|
|
QsQuery(params): QsQuery<ListSubjectsParams>,
|
|
) -> Result<Json<ListSubjectsResponse>> {
|
|
metrics::counter!("stemedb_queries_total", "endpoint" => "list_subjects").increment(1);
|
|
|
|
let prefix = if let Some(ref q) = params.q {
|
|
key_codec::subjects_index_key(q)
|
|
} else {
|
|
key_codec::subjects_scan_prefix()
|
|
};
|
|
|
|
let entries = state.store.scan_prefix(&prefix).await?;
|
|
let total_count = entries.len();
|
|
let limit = params.limit.unwrap_or(100).min(1000);
|
|
|
|
let subjects: Vec<String> = entries
|
|
.iter()
|
|
.filter_map(|(k, _)| key_codec::extract_subject_from_subjects_key(k))
|
|
.take(limit)
|
|
.collect();
|
|
|
|
Ok(Json(ListSubjectsResponse { subjects, total_count }))
|
|
}
|
|
|
|
/// List all predicates for a given subject.
|
|
///
|
|
/// Scans the `{subject}\x00SP:` index in Redb to find all predicates
|
|
/// that have been asserted for this subject.
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/v1/subjects/{subject}/predicates",
|
|
params(
|
|
("subject" = String, Path, description = "The subject to list predicates for")
|
|
),
|
|
responses(
|
|
(status = 200, description = "List of predicates for the subject", body = ListPredicatesResponse),
|
|
(status = 500, description = "Internal server error", body = crate::dto::ErrorResponse)
|
|
),
|
|
tag = "discovery"
|
|
)]
|
|
#[instrument(skip(state), fields(%subject))]
|
|
pub async fn list_predicates(
|
|
State(state): State<AppState>,
|
|
Path(subject): Path<String>,
|
|
) -> Result<Json<ListPredicatesResponse>> {
|
|
metrics::counter!("stemedb_queries_total", "endpoint" => "list_predicates").increment(1);
|
|
|
|
let prefix = key_codec::subject_predicate_scan_prefix(&subject);
|
|
let entries = state.store.scan_prefix(&prefix).await?;
|
|
|
|
let predicates: Vec<String> =
|
|
entries.iter().filter_map(|(k, _)| key_codec::extract_sp_key(k).map(|(_, p)| p)).collect();
|
|
|
|
Ok(Json(ListPredicatesResponse { subject, predicates }))
|
|
}
|