Skip to main content

redevplugin_worker_sdk/
api.rs

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