1use kora_lib::{config::ValidationConfig, transaction::TokenPriceInfo};
2use std::path::PathBuf;
3use utoipa::{
4 openapi::{
5 path::OperationBuilder, request_body::RequestBodyBuilder, ContentBuilder, PathItem,
6 PathItemType, Required, ResponseBuilder, ResponsesBuilder, ServerBuilder,
7 },
8 OpenApi,
9};
10
11use crate::{
12 method::{
13 get_blockhash::GetBlockhashResponse,
14 get_config::GetConfigResponse,
15 get_supported_tokens::GetSupportedTokensResponse,
16 sign_and_send_transaction::{
17 SignAndSendTransactionRequest, SignAndSendTransactionResponse,
18 },
19 sign_transaction::{SignTransactionRequest, SignTransactionResponse},
20 sign_transaction_if_paid::{SignTransactionIfPaidRequest, SignTransactionIfPaidResponse},
21 transfer_transaction::{TransferTransactionRequest, TransferTransactionResponse},
22 },
23 rpc::KoraRpc,
24};
25
26use super::helper::{build_error_response, request_schema};
27
28const JSON_CONTENT_TYPE: &str = "application/json";
29
30#[derive(OpenApi)]
31#[openapi(
32 info(
33 title = "kora-rpc",
34 version = "0.1.0",
35 description = "RPC server for Kora gasless relayer",
36 license(name = "MIT")
37 ),
38 components(schemas(
39 ValidationConfig,
40 TokenPriceInfo,
41 GetBlockhashResponse,
42 GetConfigResponse,
43 GetSupportedTokensResponse,
44 SignAndSendTransactionRequest,
45 SignAndSendTransactionResponse,
46 SignTransactionRequest,
47 SignTransactionResponse,
48 SignTransactionIfPaidRequest,
49 SignTransactionIfPaidResponse,
50 TransferTransactionRequest,
51 TransferTransactionResponse,
52 ))
53)]
54pub struct ApiDoc;
55
56pub fn update_docs() {
57 let method_specs = KoraRpc::build_docs_spec();
58 let mut combined_doc = ApiDoc::openapi();
59
60 let components = combined_doc.components.unwrap_or_default();
62
63 combined_doc.servers =
64 Some(vec![ServerBuilder::new().url("https://api.example.com/v1".to_string()).build()]);
65
66 for spec in method_specs {
67 let content =
68 ContentBuilder::new().schema(request_schema(&spec.name, spec.request.clone())).build();
69
70 let request_body = RequestBodyBuilder::new()
71 .content(JSON_CONTENT_TYPE, content)
72 .required(Some(Required::True))
73 .build();
74
75 let responses = ResponsesBuilder::new()
76 .response(
77 "200",
78 ResponseBuilder::new().description("Successful response").content(
79 JSON_CONTENT_TYPE,
80 ContentBuilder::new().schema(spec.response.clone()).build(),
81 ),
82 )
83 .response("429", build_error_response("Exceeded rate limit."))
84 .response("500", build_error_response("Internal server error."))
85 .build();
86
87 let operation =
88 OperationBuilder::new().request_body(Some(request_body)).responses(responses).build();
89
90 let mut path_item = PathItem::new(PathItemType::Post, operation);
91 path_item.summary = Some(spec.name.clone());
92
93 combined_doc.paths.paths.insert(format!("/{}", spec.name), path_item);
94 }
95
96 combined_doc.components = Some(components);
98
99 let json = serde_json::to_string_pretty(&combined_doc).unwrap();
100 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/openapi/spec/combined_api.json");
101
102 std::fs::write(&path, json).unwrap();
103
104 let validate_result = std::process::Command::new("swagger-cli")
105 .arg("validate")
106 .arg(path.to_str().unwrap())
107 .output()
108 .unwrap();
109
110 if !validate_result.status.success() {
111 let stderr = String::from_utf8_lossy(&validate_result.stderr);
112 panic!("Failed to validate OpenAPI schema: {}", stderr);
113 }
114}