1use axum::Json;
6use axum::http::StatusCode;
7use axum::http::header::CONTENT_TYPE;
8use axum::response::{IntoResponse, Response};
9use serde_json::Value;
10
11use super::*;
12
13mod decode;
14pub(crate) use decode::{
15 JsonRequestMode, decode_json_request, decode_json_request_with_surface_spec,
16};
17
18#[derive(Debug, Clone)]
19pub struct RawJson {
20 bytes: Bytes,
21}
22
23impl RawJson {
24 pub fn new(bytes: impl Into<Bytes>) -> Self {
25 Self {
26 bytes: bytes.into(),
27 }
28 }
29}
30
31impl IntoResponse for RawJson {
32 fn into_response(self) -> Response {
33 ([(CONTENT_TYPE, "application/json")], self.bytes).into_response()
34 }
35}
36
37pub async fn proto_request<Req, Rsp>(
44 state: &RestState,
45 proto_id: u32,
46 json_body: Option<Value>,
47) -> Result<Json<Value>, (StatusCode, Json<Value>)>
48where
49 Req: Message + Default + serde::de::DeserializeOwned,
50 Rsp: Message + Default + serde::Serialize,
51{
52 proto_request_internal::<Req, Rsp>(state, proto_id, json_body, None, None, None).await
53}
54
55pub(crate) async fn proto_request_with_surface_spec_and_idempotency<Req, Rsp>(
56 state: &RestState,
57 proto_id: u32,
58 json_body: Option<Value>,
59 idempotency_key: Option<String>,
60 surface_spec: &'static EndpointSpec,
61) -> Result<Json<Value>, (StatusCode, Json<Value>)>
62where
63 Req: Message + Default + serde::de::DeserializeOwned,
64 Rsp: Message + Default + serde::Serialize,
65{
66 proto_request_internal::<Req, Rsp>(
67 state,
68 proto_id,
69 json_body,
70 idempotency_key,
71 None,
72 Some(surface_spec),
73 )
74 .await
75}
76
77pub async fn proto_request_raw<Req, Rsp>(
78 state: &RestState,
79 proto_id: u32,
80 json_body: Option<Value>,
81) -> Result<RawJson, (StatusCode, Json<Value>)>
82where
83 Req: Message + Default + serde::de::DeserializeOwned,
84 Rsp: Message + Default + serde::Serialize,
85{
86 proto_request_raw_internal::<Req, Rsp>(
87 state,
88 proto_id,
89 json_body,
90 None,
91 None,
92 JsonRequestMode::GenericRest,
93 )
94 .await
95}
96
97pub async fn proto_request_raw_spec_body<Req, Rsp>(
98 state: &RestState,
99 proto_id: u32,
100 json_body: Option<Value>,
101) -> Result<RawJson, (StatusCode, Json<Value>)>
102where
103 Req: Message + Default + serde::de::DeserializeOwned,
104 Rsp: Message + Default + serde::Serialize,
105{
106 proto_request_raw_internal::<Req, Rsp>(
107 state,
108 proto_id,
109 json_body,
110 None,
111 None,
112 JsonRequestMode::RawSpecBody,
113 )
114 .await
115}
116
117pub(super) fn map_dispatch_error(
121 spec: &'static EndpointSpec,
122 err: DispatchError,
123) -> (StatusCode, Json<Value>) {
124 let proto_id = spec
125 .proto_id()
126 .map(|id| id.to_string())
127 .unwrap_or_else(|| "daemon-local".to_string());
128 let ret_msg = format!(
129 "{} (endpoint: {}, proto_id: {})",
130 err, spec.canonical_name, proto_id
131 );
132 let status = StatusCode::from_u16(spec.runtime.error.validation_http_status)
133 .unwrap_or(StatusCode::BAD_REQUEST);
134 let machine_error_field = spec.runtime.error.machine_error_field;
135 let mut body = serde_json::json!({
136 "ret_type": -1,
137 "ret_msg": ret_msg,
138 });
139 if let Some(obj) = body.as_object_mut() {
140 obj.insert(
141 machine_error_field.to_string(),
142 serde_json::json!({
143 "kind": "validation_error",
144 "message": err.to_string(),
145 "endpoint": spec.canonical_name,
146 "proto_id": proto_id,
147 }),
148 );
149 }
150 (status, Json(body))
151}
152
153pub(super) fn validation_error_body(message: impl Into<String>) -> Value {
154 let message = message.into();
155 serde_json::json!({
156 "ret_type": -1,
157 "ret_msg": message,
158 "error": message,
159 })
160}
161
162pub async fn proto_request_with_filter<Req, Rsp>(
176 state: &RestState,
177 proto_id: u32,
178 json_body: Option<Value>,
179 allowed_acc_ids: Option<&std::collections::HashSet<u64>>,
180) -> Result<Json<Value>, (StatusCode, Json<Value>)>
181where
182 Req: Message + Default + serde::de::DeserializeOwned,
183 Rsp: Message + Default + serde::Serialize,
184{
185 let ctx = if let Some(allowed) = allowed_acc_ids {
189 crate::caller_context::CallerContext {
190 key_id: None,
191 allowed_acc_ids: Some(std::sync::Arc::new(allowed.clone())),
192 has_auth_setup_scope: false,
193 is_loopback: false,
194 legacy_local_mode: false,
195 }
196 } else {
197 crate::caller_context::CallerContext::legacy()
198 };
199 proto_request_internal::<Req, Rsp>(state, proto_id, json_body, None, Some(&ctx), None).await
200}
201
202pub async fn proto_request_with_idempotency<Req, Rsp>(
206 state: &RestState,
207 proto_id: u32,
208 json_body: Option<Value>,
209 idempotency_key: Option<String>,
210) -> Result<Json<Value>, (StatusCode, Json<Value>)>
211where
212 Req: Message + Default + serde::de::DeserializeOwned,
213 Rsp: Message + Default + serde::Serialize,
214{
215 proto_request_internal::<Req, Rsp>(state, proto_id, json_body, idempotency_key, None, None)
216 .await
217}
218
219pub async fn proto_request_with_idempotency_and_caller<Req, Rsp>(
226 state: &RestState,
227 proto_id: u32,
228 json_body: Option<Value>,
229 idempotency_key: Option<String>,
230 caller_key_id: Option<String>,
231) -> Result<Json<Value>, (StatusCode, Json<Value>)>
232where
233 Req: Message + Default + serde::de::DeserializeOwned,
234 Rsp: Message + Default + serde::Serialize,
235{
236 let ctx = caller_key_id.map(|k| crate::caller_context::CallerContext {
237 key_id: Some(k),
238 allowed_acc_ids: None,
239 has_auth_setup_scope: false,
240 is_loopback: false,
241 legacy_local_mode: false,
242 });
243 proto_request_internal::<Req, Rsp>(
244 state,
245 proto_id,
246 json_body,
247 idempotency_key,
248 ctx.as_ref(),
249 None,
250 )
251 .await
252}
253
254pub async fn proto_request_with_ctx<Req, Rsp>(
273 state: &RestState,
274 proto_id: u32,
275 json_body: Option<Value>,
276 idempotency_key: Option<String>,
277 ctx: Option<&crate::caller_context::CallerContext>,
278) -> Result<Json<Value>, (StatusCode, Json<Value>)>
279where
280 Req: Message + Default + serde::de::DeserializeOwned,
281 Rsp: Message + Default + serde::Serialize,
282{
283 proto_request_internal::<Req, Rsp>(state, proto_id, json_body, idempotency_key, ctx, None).await
284}
285
286async fn proto_request_raw_internal<Req, Rsp>(
287 state: &RestState,
288 proto_id: u32,
289 json_body: Option<Value>,
290 idempotency_key: Option<String>,
291 ctx: Option<&crate::caller_context::CallerContext>,
292 mode: JsonRequestMode,
293) -> Result<RawJson, (StatusCode, Json<Value>)>
294where
295 Req: Message + Default + serde::de::DeserializeOwned,
296 Rsp: Message + Default + serde::Serialize,
297{
298 let req_msg: Req = decode_json_request(proto_id, json_body, mode)?;
299 let conn_id = state.next_conn_id();
300 let serial_no = state.next_serial();
301 let body = futu_server::trade_packet_id::fill_omitted_trade_packet_id_bytes(
302 proto_id,
303 req_msg.encode_to_vec(),
304 conn_id,
305 serial_no,
306 )
307 .map(Bytes::from)
308 .map_err(|e| {
309 (
310 StatusCode::INTERNAL_SERVER_ERROR,
311 Json(serde_json::json!({
312 "error": format!("failed to prepare trade PacketID: {e}")
313 })),
314 )
315 })?;
316
317 let incoming =
318 IncomingRequest::builder(conn_id, proto_id, serial_no, ProtoFmtType::Protobuf, body)
319 .with_idempotency_key(idempotency_key)
320 .with_caller_scope(
321 ctx.and_then(|c| c.caller_allowed_acc_ids_arc()),
322 ctx.and_then(|c| c.caller_key_id()),
323 )
324 .with_auth_setup_admission(
325 ctx.is_some_and(|c| c.caller_has_auth_setup_scope()),
326 ctx.is_some_and(|c| c.caller_is_loopback()),
327 ctx.is_some_and(|c| c.caller_legacy_local_mode()),
328 )
329 .build();
330
331 let resp_bytes = state
332 .router
333 .dispatch(incoming.conn_id, &incoming)
334 .await
335 .ok_or_else(|| {
336 (
337 StatusCode::INTERNAL_SERVER_ERROR,
338 Json(serde_json::json!({
339 "error": "handler returned no response"
340 })),
341 )
342 })?;
343
344 let resp_bytes = state.filter_registry.apply(
345 proto_id,
346 resp_bytes,
347 ctx.and_then(|c| c.allowed_acc_ids_borrow()),
348 );
349
350 let rsp_msg = Rsp::decode(Bytes::from(resp_bytes)).map_err(|e| {
351 (
352 StatusCode::INTERNAL_SERVER_ERROR,
353 Json(serde_json::json!({
354 "error": format!("failed to decode response: {e}")
355 })),
356 )
357 })?;
358
359 raw_json_from_proto_response(&rsp_msg)
360}
361
362pub(crate) fn raw_json_from_proto_response<Rsp>(
363 rsp_msg: &Rsp,
364) -> Result<RawJson, (StatusCode, Json<Value>)>
365where
366 Rsp: serde::Serialize,
367{
368 let encoded = encode_proto_response_raw_or_error_value(rsp_msg).map_err(|e| {
369 (
370 StatusCode::INTERNAL_SERVER_ERROR,
371 Json(serde_json::json!({
372 "error": e
373 })),
374 )
375 })?;
376 match encoded {
377 ProtoJsonBody::Raw(raw) => Ok(RawJson::new(raw)),
378 ProtoJsonBody::Value(value) => {
379 let raw = serde_json::to_vec(&value).map_err(|e| {
380 (
381 StatusCode::INTERNAL_SERVER_ERROR,
382 Json(serde_json::json!({
383 "error": format!("failed to serialize response: {e}")
384 })),
385 )
386 })?;
387 Ok(RawJson::new(raw))
388 }
389 }
390}
391
392async fn proto_request_internal<Req, Rsp>(
397 state: &RestState,
398 proto_id: u32,
399 json_body: Option<Value>,
400 idempotency_key: Option<String>,
401 ctx: Option<&crate::caller_context::CallerContext>,
402 surface_spec: Option<&'static EndpointSpec>,
403) -> Result<Json<Value>, (StatusCode, Json<Value>)>
404where
405 Req: Message + Default + serde::de::DeserializeOwned,
406 Rsp: Message + Default + serde::Serialize,
407{
408 let req_msg: Req = decode_json_request_with_surface_spec(
412 proto_id,
413 json_body,
414 JsonRequestMode::GenericRest,
415 surface_spec,
416 )?;
417
418 let conn_id = state.next_conn_id();
420 let serial_no = state.next_serial();
421 let body = futu_server::trade_packet_id::fill_omitted_trade_packet_id_bytes(
422 proto_id,
423 req_msg.encode_to_vec(),
424 conn_id,
425 serial_no,
426 )
427 .map(Bytes::from)
428 .map_err(|e| {
429 (
430 StatusCode::INTERNAL_SERVER_ERROR,
431 Json(serde_json::json!({
432 "error": format!("failed to prepare trade PacketID: {e}")
433 })),
434 )
435 })?;
436
437 let incoming =
441 IncomingRequest::builder(conn_id, proto_id, serial_no, ProtoFmtType::Protobuf, body)
442 .with_idempotency_key(idempotency_key)
443 .with_caller_scope(
444 ctx.and_then(|c| c.caller_allowed_acc_ids_arc()),
445 ctx.and_then(|c| c.caller_key_id()),
446 )
447 .with_auth_setup_admission(
448 ctx.is_some_and(|c| c.caller_has_auth_setup_scope()),
449 ctx.is_some_and(|c| c.caller_is_loopback()),
450 ctx.is_some_and(|c| c.caller_legacy_local_mode()),
451 )
452 .build();
453
454 let resp_bytes = state
455 .router
456 .dispatch(incoming.conn_id, &incoming)
457 .await
458 .ok_or_else(|| {
459 (
460 StatusCode::INTERNAL_SERVER_ERROR,
461 Json(serde_json::json!({
462 "error": "handler returned no response"
463 })),
464 )
465 })?;
466
467 let resp_bytes = state.filter_registry.apply(
474 proto_id,
475 resp_bytes,
476 ctx.and_then(|c| c.allowed_acc_ids_borrow()),
477 );
478
479 let rsp_msg = Rsp::decode(Bytes::from(resp_bytes)).map_err(|e| {
481 (
482 StatusCode::INTERNAL_SERVER_ERROR,
483 Json(serde_json::json!({
484 "error": format!("failed to decode response: {e}")
485 })),
486 )
487 })?;
488
489 let mut json_rsp = serde_json::to_value(&rsp_msg).map_err(|e| {
491 (
492 StatusCode::INTERNAL_SERVER_ERROR,
493 Json(serde_json::json!({
494 "error": format!("failed to serialize response: {e}")
495 })),
496 )
497 })?;
498
499 maybe_wrap_err_code_prefix(&mut json_rsp);
506
507 Ok(Json(json_rsp))
508}
509
510pub(super) enum ProtoJsonBody {
511 Raw(Bytes),
512 Value(Value),
513}
514
515pub(super) fn encode_proto_response_raw_or_error_value<Rsp>(
516 rsp_msg: &Rsp,
517) -> Result<ProtoJsonBody, String>
518where
519 Rsp: serde::Serialize,
520{
521 let raw =
522 serde_json::to_vec(rsp_msg).map_err(|e| format!("failed to serialize response: {e}"))?;
523 if serialized_ret_type(&raw) == Some(0) {
524 return Ok(ProtoJsonBody::Raw(Bytes::from(raw)));
525 }
526
527 let mut json_rsp =
528 serde_json::to_value(rsp_msg).map_err(|e| format!("failed to serialize response: {e}"))?;
529 maybe_wrap_err_code_prefix(&mut json_rsp);
530 Ok(ProtoJsonBody::Value(json_rsp))
531}
532
533fn serialized_ret_type(raw: &[u8]) -> Option<i64> {
534 #[derive(serde::Deserialize)]
535 struct RetOnly {
536 ret_type: Option<i64>,
537 }
538
539 serde_json::from_slice::<RetOnly>(raw)
540 .ok()
541 .and_then(|ret| ret.ret_type)
542}
543
544pub(crate) fn maybe_wrap_err_code_prefix(v: &mut Value) {
559 let obj = match v.as_object_mut() {
560 Some(o) => o,
561 None => return,
562 };
563 let is_err = obj
565 .get("ret_type")
566 .and_then(|t| t.as_i64())
567 .map(|t| t != 0)
568 .unwrap_or(false);
569 if !is_err {
570 return;
571 }
572 let raw_msg = obj
574 .get("ret_msg")
575 .and_then(|m| m.as_str())
576 .unwrap_or("")
577 .to_string();
578 if raw_msg.starts_with("[err_code=") {
580 return;
581 }
582 let err_code_label = match obj.get("err_code") {
584 Some(Value::Number(n)) => n
585 .as_i64()
586 .map(|i| i.to_string())
587 .unwrap_or("none".to_string()),
588 _ => "none".to_string(),
589 };
590 let new_msg = if raw_msg.is_empty() {
591 format!("[err_code={err_code_label}]")
592 } else {
593 format!("[err_code={err_code_label}] {raw_msg}")
594 };
595 obj.insert("ret_msg".to_string(), Value::String(new_msg));
596}