1use axum::{
8 extract::{rejection::JsonRejection, Extension, Query, State},
9 response::{IntoResponse, Json, Response},
10 routing::{get, post},
11 Router,
12};
13use soma_application::CatalogSnapshot;
14use soma_http_api::response::json_rejection_response;
15
16use crate::{
17 auth::{palette_execution_context, AuthContext},
18 catalog::catalog_response,
19 dto::{
20 LauncherExecuteRequest, LauncherSchemaQuery, LauncherSearchQuery, LauncherSearchResponse,
21 },
22 error::{launcher_not_found, palette_error_response},
23 execute::{execute_launcher, ExecuteOutcome},
24 schema::find_schema,
25 search::search_entries,
26 state::PaletteState,
27};
28
29pub fn router() -> Router<PaletteState> {
30 Router::new()
31 .route("/v1/palette/catalog", get(get_catalog))
32 .route("/v1/palette/search", get(get_search))
33 .route("/v1/palette/schema", get(get_schema))
34 .route("/v1/palette/execute", post(post_execute))
35}
36
37async fn refreshed_snapshot(state: &PaletteState) -> Result<CatalogSnapshot, Response> {
44 state
45 .application()
46 .refresh_providers()
47 .map_err(palette_error_response)
48}
49
50async fn get_catalog(State(state): State<PaletteState>) -> Response {
51 let snapshot = match refreshed_snapshot(&state).await {
52 Ok(snapshot) => snapshot,
53 Err(response) => return response,
54 };
55 Json(catalog_response(&snapshot)).into_response()
56}
57
58async fn get_search(
59 State(state): State<PaletteState>,
60 Query(query): Query<LauncherSearchQuery>,
61) -> Response {
62 let snapshot = match refreshed_snapshot(&state).await {
63 Ok(snapshot) => snapshot,
64 Err(response) => return response,
65 };
66 let entries = crate::catalog::palette_entries(&snapshot);
67 let results = search_entries(&entries, &query.q, query.limit);
68 Json(LauncherSearchResponse { entries: results }).into_response()
69}
70
71async fn get_schema(
72 State(state): State<PaletteState>,
73 Query(query): Query<LauncherSchemaQuery>,
74) -> Response {
75 let snapshot = match refreshed_snapshot(&state).await {
76 Ok(snapshot) => snapshot,
77 Err(response) => return response,
78 };
79 match find_schema(&snapshot, &query.id) {
80 Some(schema) => Json(schema).into_response(),
81 None => launcher_not_found(&query.id),
82 }
83}
84
85async fn post_execute(
86 State(state): State<PaletteState>,
87 auth: Option<Extension<AuthContext>>,
88 body: Result<Json<LauncherExecuteRequest>, JsonRejection>,
89) -> Response {
90 let Json(request) = match body {
91 Ok(body) => body,
92 Err(error) => return json_rejection_response(error),
97 };
98 let context = palette_execution_context(&state, auth.as_ref().map(|Extension(auth)| auth));
99 let id = request.id.clone();
100
101 match execute_launcher(&state, request, context).await {
102 ExecuteOutcome::Ok(response) => Json(response).into_response(),
103 ExecuteOutcome::NotFound => launcher_not_found(&id),
104 ExecuteOutcome::Failed(error) => palette_error_response(error),
105 }
106}
107
108#[cfg(test)]
109#[path = "router_tests.rs"]
110mod tests;