Skip to main content

codex_runtime/runtime/core/
rpc.rs

1use serde::{de::DeserializeOwned, Serialize};
2use serde_json::Value;
3use tokio::time::Duration;
4
5use crate::runtime::errors::{RpcError, RuntimeError};
6use crate::runtime::rpc_contract::{
7    validate_rpc_request, validate_rpc_response, RpcValidationMode,
8};
9
10use super::rpc_io::{call_raw_inner, notify_raw_inner};
11use super::Runtime;
12
13impl Runtime {
14    pub async fn call_raw(&self, method: &str, params: Value) -> Result<Value, RpcError> {
15        self.call_raw_internal(method, params, true, self.inner.spec.rpc_response_timeout)
16            .await
17    }
18
19    /// JSON-RPC call with contract validation for known methods.
20    /// Validation covers request params before send and result shape after receive.
21    pub async fn call_validated(&self, method: &str, params: Value) -> Result<Value, RpcError> {
22        self.call_validated_with_mode(method, params, RpcValidationMode::KnownMethods)
23            .await
24    }
25
26    /// JSON-RPC call with explicit validation mode.
27    pub async fn call_validated_with_mode(
28        &self,
29        method: &str,
30        params: Value,
31        mode: RpcValidationMode,
32    ) -> Result<Value, RpcError> {
33        self.call_validated_with_mode_and_timeout(
34            method,
35            params,
36            mode,
37            self.inner.spec.rpc_response_timeout,
38        )
39        .await
40    }
41
42    pub(crate) async fn call_validated_with_mode_and_timeout(
43        &self,
44        method: &str,
45        params: Value,
46        mode: RpcValidationMode,
47        timeout_duration: Duration,
48    ) -> Result<Value, RpcError> {
49        validate_rpc_request(method, &params, mode)?;
50        let result = self
51            .call_raw_internal(method, params, true, timeout_duration)
52            .await?;
53        validate_rpc_response(method, &result, mode)?;
54        Ok(result)
55    }
56
57    #[cfg(test)]
58    pub(crate) async fn call_raw_with_timeout(
59        &self,
60        method: &str,
61        params: Value,
62        timeout_duration: Duration,
63    ) -> Result<Value, RpcError> {
64        self.call_raw_internal(method, params, true, timeout_duration)
65            .await
66    }
67
68    /// Typed JSON-RPC call with known-method contract validation.
69    pub async fn call_typed_validated<P, R>(&self, method: &str, params: P) -> Result<R, RpcError>
70    where
71        P: Serialize,
72        R: DeserializeOwned,
73    {
74        self.call_typed_validated_with_mode(method, params, RpcValidationMode::KnownMethods)
75            .await
76    }
77
78    /// Typed JSON-RPC call with explicit validation mode.
79    pub async fn call_typed_validated_with_mode<P, R>(
80        &self,
81        method: &str,
82        params: P,
83        mode: RpcValidationMode,
84    ) -> Result<R, RpcError>
85    where
86        P: Serialize,
87        R: DeserializeOwned,
88    {
89        let params_value = serde_json::to_value(params).map_err(|err| {
90            RpcError::InvalidRequest(format!(
91                "failed to serialize json-rpc params for {method}: {err}"
92            ))
93        })?;
94        let result = self
95            .call_validated_with_mode(method, params_value, mode)
96            .await?;
97        serde_json::from_value(result).map_err(|err| {
98            RpcError::InvalidRequest(format!(
99                "failed to deserialize json-rpc result for {method}: {err}"
100            ))
101        })
102    }
103
104    pub async fn notify_raw(&self, method: &str, params: Value) -> Result<(), RuntimeError> {
105        self.notify_raw_internal(method, params, true).await
106    }
107
108    /// JSON-RPC notify with known-method request validation.
109    pub async fn notify_validated(&self, method: &str, params: Value) -> Result<(), RuntimeError> {
110        self.notify_validated_with_mode(method, params, RpcValidationMode::KnownMethods)
111            .await
112    }
113
114    /// JSON-RPC notify with explicit validation mode.
115    pub async fn notify_validated_with_mode(
116        &self,
117        method: &str,
118        params: Value,
119        mode: RpcValidationMode,
120    ) -> Result<(), RuntimeError> {
121        validate_rpc_request(method, &params, mode).map_err(|err| {
122            RuntimeError::InvalidConfig(format!("invalid json-rpc notify payload: {err}"))
123        })?;
124        self.notify_raw_internal(method, params, true).await
125    }
126
127    /// Typed JSON-RPC notify with known-method request validation.
128    pub async fn notify_typed_validated<P>(
129        &self,
130        method: &str,
131        params: P,
132    ) -> Result<(), RuntimeError>
133    where
134        P: Serialize,
135    {
136        self.notify_typed_validated_with_mode(method, params, RpcValidationMode::KnownMethods)
137            .await
138    }
139
140    /// Typed JSON-RPC notify with explicit validation mode.
141    pub async fn notify_typed_validated_with_mode<P>(
142        &self,
143        method: &str,
144        params: P,
145        mode: RpcValidationMode,
146    ) -> Result<(), RuntimeError>
147    where
148        P: Serialize,
149    {
150        let params_value = serde_json::to_value(params).map_err(|err| {
151            RuntimeError::InvalidConfig(format!(
152                "invalid json-rpc notify payload: failed to serialize json-rpc params for {method}: {err}"
153            ))
154        })?;
155        self.notify_validated_with_mode(method, params_value, mode)
156            .await
157    }
158
159    async fn call_raw_internal(
160        &self,
161        method: &str,
162        params: Value,
163        require_initialized: bool,
164        timeout_duration: Duration,
165    ) -> Result<Value, RpcError> {
166        if require_initialized && !self.is_initialized() {
167            return Err(RpcError::InvalidRequest(
168                "runtime is not initialized".to_owned(),
169            ));
170        }
171
172        call_raw_inner(&self.inner, method, params, timeout_duration).await
173    }
174
175    async fn notify_raw_internal(
176        &self,
177        method: &str,
178        params: Value,
179        require_initialized: bool,
180    ) -> Result<(), RuntimeError> {
181        if require_initialized && !self.is_initialized() {
182            return Err(RuntimeError::NotInitialized);
183        }
184
185        notify_raw_inner(&self.inner, method, params).await
186    }
187}