/
mcmare
/
RustAPI
Обзор
Документация
Войти
/
mcmare
/
RustAPI
Код
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
engine/src/openapi.rs
138 строк
5 KB
mcmare
Add input/output schema for script endpoints, redesign WebUI with Tailwind
19 июл 2026, 09:28
19 июл 2026, 09:28
f3df0d8
Код
Авторство
О чём код?
use axum::{extract::State, response::Html, routing::get, Json, Router}; use serde_json::{json, Map, Value}; use std::sync::Arc; use crate::{ config::{CrudOperation, EndpointDef, EndpointKind, FieldType, HttpMethod}, dispatch::EngineState, }; fn field_schema(ty: &FieldType) -> Value { match ty { FieldType::String => json!({ "type": "string" }), FieldType::Number => json!({ "type": "number" }), FieldType::Bool => json!({ "type": "boolean" }), FieldType::Uuid => json!({ "type": "string", "format": "uuid" }), FieldType::Datetime => json!({ "type": "string", "format": "date-time" }), FieldType::Object => json!({ "type": "object" }), FieldType::Array(inner) => json!({ "type": "array", "items": field_schema(inner) }), } } fn schema_object(fields: &std::collections::HashMap<String, FieldType>) -> Value { let properties: Map<String, Value> = fields.iter().map(|(k, v)| (k.clone(), field_schema(v))).collect(); json!({ "type": "object", "properties": properties }) } fn method_key(m: HttpMethod) -> &'static str { match m { HttpMethod::Get => "get", HttpMethod::Post => "post", HttpMethod::Put => "put", HttpMethod::Patch => "patch", HttpMethod::Delete => "delete", } } fn operation_for(ep: &EndpointDef) -> Value { match &ep.kind { EndpointKind::Crud { operation, input, output, .. } => { let mut op = json!({ "summary": format!("{operation:?} {}", ep.path), "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": schema_object(output) } } } } }); if matches!(operation, CrudOperation::Create | CrudOperation::Update) && !input.is_empty() { op["requestBody"] = json!({ "required": true, "content": { "application/json": { "schema": schema_object(input) } } }); } op } EndpointKind::Script { script_path, input, output } => { let request_schema = if input.is_empty() { json!({ "type": "object" }) } else { schema_object(input) }; let mut op = json!({ "summary": format!("script: {}", script_path.display()), "requestBody": { "content": { "application/json": { "schema": request_schema } } }, "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": schema_object(output) } } } } }); if output.is_empty() { op["responses"]["200"] = json!({ "description": "OK" }); } op } } } /// Builds an OpenAPI 3.0 document from the currently live endpoint list — regenerated on /// every request, so it always reflects the latest hot-reloaded config. pub fn generate(endpoints: &[Arc<EndpointDef>]) -> Value { let mut paths: Map<String, Value> = Map::new(); for ep in endpoints { let entry = paths.entry(ep.path.clone()).or_insert_with(|| json!({})); entry[method_key(ep.method)] = operation_for(ep); } json!({ "openapi": "3.0.3", "info": { "title": "rustapi", "version": "0.1.0" }, "paths": Value::Object(paths), "components": { "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer" }, "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" } } }, "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }] }) } async fn spec_handler(State(state): State<EngineState>) -> Json<Value> { Json(generate(&state.routes.load().endpoints)) } async fn docs_handler() -> Html<&'static str> { Html(DOCS_HTML) } // Swagger UI is loaded from a CDN, pinned to an exact version (not a floating `@5` tag) // with `crossorigin` set so the browser enforces CORS on the fetch. A `integrity="sha384-…"` // attribute would harden this further, but the correct hash for this asset couldn't be // verified from this environment (no network access) — add one before relying on this // page in production, or vendor swagger-ui-dist locally instead of using the CDN. const DOCS_HTML: &str = r#"<!doctype html> <html> <head> <title>rustapi docs</title> <link rel="stylesheet" crossorigin="anonymous" href="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui.css"> </head> <body> <div id="swagger-ui"></div> <script crossorigin="anonymous" src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-bundle.js"></script> <script> window.onload = () => { window.ui = SwaggerUIBundle({ url: '/openapi.json', dom_id: '#swagger-ui' }); }; </script> </body> </html>"#; /// `/openapi.json` + `/docs`, deliberately outside the auth-protected router — API /// documentation is meant to be browsable without a token, same as `/health`. pub fn docs_router(state: EngineState) -> Router { Router::new() .route("/openapi.json", get(spec_handler)) .route("/docs", get(docs_handler)) .with_state(state) }