Skip to main content

recoco_core/
server.rs

1// ReCoco is a Rust-only fork of CocoIndex, by [CocoIndex](https://CocoIndex)
2// Original code from CocoIndex is copyrighted by CocoIndex
3// SPDX-FileCopyrightText: 2025-2026 CocoIndex (upstream)
4// SPDX-FileContributor: CocoIndex Contributors
5//
6// All modifications from the upstream for ReCoco are copyrighted by Knitli Inc.
7// SPDX-FileCopyrightText: 2026 Knitli Inc. (ReCoco)
8// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
9//
10// Both the upstream CocoIndex code and the ReCoco modifications are licensed under the Apache-2.0 License.
11// SPDX-License-Identifier: Apache-2.0
12
13use crate::prelude::*;
14
15use crate::{lib_context::LibContext, service};
16use axum::response::Json;
17use axum::{Router, routing};
18use tower::ServiceBuilder;
19use tower_http::{
20    cors::{AllowOrigin, CorsLayer},
21    trace::TraceLayer,
22};
23
24#[derive(Deserialize, Debug)]
25pub struct ServerSettings {
26    pub address: String,
27    #[serde(default)]
28    pub cors_origins: Vec<String>,
29}
30
31/// Initialize the server and return a future that will actually handle requests.
32pub async fn init_server(
33    lib_context: Arc<LibContext>,
34    settings: ServerSettings,
35) -> Result<BoxFuture<'static, ()>> {
36    let mut cors = CorsLayer::default();
37    if !settings.cors_origins.is_empty() {
38        let origins: Vec<_> = settings
39            .cors_origins
40            .iter()
41            .map(|origin| origin.parse())
42            .collect::<std::result::Result<_, _>>()?;
43        cors = cors
44            .allow_origin(AllowOrigin::list(origins))
45            .allow_methods([
46                axum::http::Method::GET,
47                axum::http::Method::POST,
48                axum::http::Method::DELETE,
49            ])
50            .allow_headers([axum::http::header::CONTENT_TYPE]);
51    }
52    let app = Router::new()
53        .route("/healthz", routing::get(healthz))
54        .route(
55            "/cocoindex",
56            routing::get(|| async { "CocoIndex is running!" }),
57        )
58        .nest(
59            "/cocoindex/api",
60            Router::new()
61                .route("/flows", routing::get(service::flows::list_flows))
62                .route(
63                    "/flows/{flowInstName}",
64                    routing::get(service::flows::get_flow),
65                )
66                .route(
67                    "/flows/{flowInstName}/schema",
68                    routing::get(service::flows::get_flow_schema),
69                )
70                .route(
71                    "/flows/{flowInstName}/keys",
72                    routing::get(service::flows::get_keys),
73                )
74                .route(
75                    "/flows/{flowInstName}/data",
76                    routing::get(service::flows::evaluate_data),
77                )
78                .route(
79                    "/flows/{flowInstName}/queryHandlers/{queryHandlerName}",
80                    routing::get(service::flows::query),
81                )
82                .route(
83                    "/flows/{flowInstName}/rowStatus",
84                    routing::get(service::flows::get_row_indexing_status),
85                )
86                .route(
87                    "/flows/{flowInstName}/update",
88                    routing::post(service::flows::update),
89                )
90                .layer(
91                    ServiceBuilder::new()
92                        .layer(TraceLayer::new_for_http())
93                        .layer(cors),
94                )
95                .with_state(lib_context.clone()),
96        );
97
98    let listener = tokio::net::TcpListener::bind(&settings.address)
99        .await
100        .map_err(Error::from)
101        .with_context(|| format!("Failed to bind to address: {}", settings.address))?;
102
103    println!(
104        "Server running at http://{}/cocoindex",
105        listener.local_addr()?
106    );
107    let serve_fut = async { axum::serve(listener, app).await.unwrap() };
108    Ok(serve_fut.boxed())
109}
110
111async fn healthz() -> Json<serde_json::Value> {
112    Json(serde_json::json!({
113        "status": "ok",
114        "version": env!("CARGO_PKG_VERSION"),
115    }))
116}