Skip to main content

redevplugin_worker_sdk/
api.rs

1use crate::PLUGIN_API;
2use crate::error::{Error, Result};
3use crate::resource::call_control_raw;
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8#[derive(Serialize)]
9struct ControlRequest<'a, Arguments> {
10    plugin_api: u16,
11    operation: &'a str,
12    arguments: &'a Arguments,
13}
14
15#[derive(serde::Deserialize)]
16struct ControlResponse {
17    ok: bool,
18    #[serde(default)]
19    result: Option<Value>,
20    #[serde(default)]
21    error: Option<Error>,
22}
23
24pub(crate) fn call<Arguments, ResultValue>(
25    operation: &str,
26    arguments: &Arguments,
27) -> Result<ResultValue>
28where
29    Arguments: Serialize,
30    ResultValue: DeserializeOwned,
31{
32    let request = serde_json::to_vec(&ControlRequest {
33        plugin_api: PLUGIN_API,
34        operation,
35        arguments,
36    })
37    .map_err(|error| Error::internal(format!("encode Worker API request: {error}")))?;
38    let response = call_control_raw(&request)?;
39    decode_response(&response)
40}
41
42fn decode_response<ResultValue>(raw: &[u8]) -> Result<ResultValue>
43where
44    ResultValue: DeserializeOwned,
45{
46    let response: ControlResponse = serde_json::from_slice(raw)
47        .map_err(|error| Error::internal(format!("decode Worker API response: {error}")))?;
48    match (response.ok, response.result, response.error) {
49        (true, Some(result), _) => serde_json::from_value(result)
50            .map_err(|error| Error::internal(format!("decode typed Worker API result: {error}"))),
51        (false, _, Some(error)) if !error.message.trim().is_empty() => Err(error),
52        _ => Err(Error::internal("Worker API response branch is incomplete")),
53    }
54}
55
56#[derive(Debug, Clone, Deserialize)]
57pub struct Context {
58    pub plugin_id: String,
59    pub plugin_version: String,
60    pub scope_kind: String,
61}
62
63#[derive(Debug, Clone, Deserialize)]
64pub struct Limits {
65    pub control_response_bytes: u64,
66    pub io_chunk_bytes: u64,
67    pub open_files_min: u32,
68    pub open_connections_min: u32,
69    pub open_watches_min: u32,
70}
71
72#[derive(Debug, Clone, Deserialize)]
73pub struct Capabilities {
74    pub plugin_api: u16,
75    #[serde(default)]
76    pub features: Vec<String>,
77    pub limits: Limits,
78}
79
80pub fn context() -> Result<Context> {
81    call("platform.context", &serde_json::json!({}))
82}
83
84pub fn capabilities() -> Result<Capabilities> {
85    call("platform.capabilities", &serde_json::json!({}))
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use serde_json::json;
92
93    #[test]
94    fn control_request_uses_the_single_plugin_api_identity() {
95        let request = serde_json::to_value(ControlRequest {
96            plugin_api: PLUGIN_API,
97            operation: "storage.sqlite",
98            arguments: &json!({"operation": "query"}),
99        })
100        .unwrap();
101        assert_eq!(
102            request,
103            json!({
104                "plugin_api": PLUGIN_API,
105                "operation": "storage.sqlite",
106                "arguments": {"operation": "query"}
107            })
108        );
109    }
110
111    #[test]
112    fn new_control_reader_tolerates_unknown_optional_fields() {
113        let value: Value = decode_response(
114            br#"{"ok":true,"result":{"plugin_api":1,"future":true},"future_envelope":7}"#,
115        )
116        .unwrap();
117        assert_eq!(value["plugin_api"], 1);
118        let error = decode_response::<Value>(
119            br#"{"ok":false,"error":{"code":"TIMEOUT","message":"late","retryable":true,"details":{},"future":1},"future_envelope":7}"#,
120        )
121        .unwrap_err();
122        assert_eq!(error.code, crate::error::ErrorCode::Timeout);
123        assert!(error.retryable);
124    }
125}