Skip to main content

redevplugin_worker_sdk/
data_upgrade.rs

1//! Data conversion is available only inside the declared upgrade Worker.
2//! Input is read-only; output belongs to an unpublished generation. Ordinary
3//! resources, credentials and network APIs are unavailable in this invocation.
4
5use crate::{api, error::Result, storage};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::BTreeMap;
9
10#[derive(Clone, Copy, Debug, Serialize)]
11#[serde(rename_all = "snake_case")]
12pub enum Dataset {
13    Input,
14    Output,
15}
16
17#[derive(Debug, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct Parameters {
20    pub from_version: String,
21    pub to_version: String,
22    pub scope: String,
23}
24
25#[derive(Debug, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct Settings {
28    pub scope: String,
29    pub revision: u64,
30    pub values: BTreeMap<String, Value>,
31}
32
33#[derive(Serialize)]
34struct Operation<'a, T> {
35    operation: &'a str,
36    #[serde(flatten)]
37    request: &'a T,
38}
39
40#[derive(Serialize)]
41struct Arguments<'a, T> {
42    dataset: Dataset,
43    kind: &'a str,
44    request: Operation<'a, T>,
45}
46
47fn call<T: Serialize, R: for<'de> Deserialize<'de>>(
48    dataset: Dataset,
49    kind: &str,
50    operation: &str,
51    request: &T,
52) -> Result<R> {
53    api::call(
54        "data.upgrade",
55        &Arguments {
56            dataset,
57            kind,
58            request: Operation { operation, request },
59        },
60    )
61}
62
63pub fn read_settings(dataset: Dataset) -> Result<Settings> {
64    call(dataset, "settings", "read", &serde_json::json!({}))
65}
66
67pub fn write_settings(values: BTreeMap<String, Value>) -> Result<()> {
68    let _: Value = call(
69        Dataset::Output,
70        "settings",
71        "write",
72        &serde_json::json!({"values": values}),
73    )?;
74    Ok(())
75}
76
77pub mod files {
78    use super::*;
79    pub fn read(
80        dataset: Dataset,
81        request: storage::files::ReadRequest,
82    ) -> Result<storage::files::ReadResponse> {
83        call(dataset, "files", "read", &request)
84    }
85    pub fn write(request: storage::files::WriteRequest) -> Result<storage::files::WriteResponse> {
86        call(Dataset::Output, "files", "write", &request)
87    }
88    pub fn delete(
89        request: storage::files::DeleteRequest,
90    ) -> Result<storage::files::DeleteResponse> {
91        call(Dataset::Output, "files", "delete", &request)
92    }
93    pub fn list(
94        dataset: Dataset,
95        request: storage::files::ListRequest,
96    ) -> Result<storage::files::ListResponse> {
97        call(dataset, "files", "list", &request)
98    }
99}
100
101pub mod kv {
102    use super::*;
103    pub fn get(
104        dataset: Dataset,
105        request: storage::kv::GetRequest,
106    ) -> Result<storage::kv::GetResponse> {
107        call(dataset, "kv", "get", &request)
108    }
109    pub fn put(request: storage::kv::PutRequest) -> Result<storage::kv::PutResponse> {
110        call(Dataset::Output, "kv", "put", &request)
111    }
112    pub fn delete(request: storage::kv::DeleteRequest) -> Result<storage::kv::DeleteResponse> {
113        call(Dataset::Output, "kv", "delete", &request)
114    }
115    pub fn list(
116        dataset: Dataset,
117        request: storage::kv::ListRequest,
118    ) -> Result<storage::kv::ListResponse> {
119        call(dataset, "kv", "list", &request)
120    }
121}
122
123pub mod sqlite {
124    use super::*;
125    pub fn query(
126        dataset: Dataset,
127        request: storage::sqlite::QueryRequest,
128    ) -> Result<storage::sqlite::QueryResponse> {
129        call(dataset, "sqlite", "query", &request)
130    }
131    pub fn exec(request: storage::sqlite::ExecRequest) -> Result<storage::sqlite::ExecResponse> {
132        call(Dataset::Output, "sqlite", "exec", &request)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    fn round_trip<T: Serialize + for<'de> Deserialize<'de>>(value: Value) -> Value {
141        serde_json::to_value(serde_json::from_value::<T>(value).unwrap()).unwrap()
142    }
143
144    #[test]
145    fn shared_fixtures_match_author_storage_types() {
146        let fixtures: Value =
147            serde_json::from_str(include_str!("../testdata/data-upgrade-fixtures.json")).unwrap();
148        for fixture in fixtures["valid"].as_array().unwrap() {
149            let envelope = &fixture["request"];
150            let args = &envelope["arguments"];
151            let kind = args["kind"].as_str().unwrap();
152            let mut request = args["request"].clone();
153            let operation = request
154                .as_object_mut()
155                .unwrap()
156                .remove("operation")
157                .unwrap();
158            let operation = operation.as_str().unwrap();
159            let encoded = match (kind, operation) {
160                ("files", "read") => round_trip::<storage::files::ReadRequest>(request),
161                ("files", "write") => round_trip::<storage::files::WriteRequest>(request),
162                ("files", "delete") => round_trip::<storage::files::DeleteRequest>(request),
163                ("files", "list") => round_trip::<storage::files::ListRequest>(request),
164                ("kv", "get") => round_trip::<storage::kv::GetRequest>(request),
165                ("kv", "put") => round_trip::<storage::kv::PutRequest>(request),
166                ("kv", "delete") => round_trip::<storage::kv::DeleteRequest>(request),
167                ("kv", "list") => round_trip::<storage::kv::ListRequest>(request),
168                ("sqlite", "query") => round_trip::<storage::sqlite::QueryRequest>(request),
169                ("sqlite", "exec") => round_trip::<storage::sqlite::ExecRequest>(request),
170                ("settings", "read" | "write") => request,
171                _ => panic!("uncovered canonical operation"),
172            };
173            let dataset = match args["dataset"].as_str().unwrap() {
174                "input" => Dataset::Input,
175                "output" => Dataset::Output,
176                _ => panic!("invalid dataset"),
177            };
178            let actual = serde_json::to_value(Arguments {
179                dataset,
180                kind,
181                request: Operation {
182                    operation,
183                    request: &encoded,
184                },
185            })
186            .unwrap();
187            assert_eq!(&actual, args, "{}", fixture["name"]);
188        }
189    }
190
191    #[test]
192    fn upgrade_reuses_storage_requests_with_explicit_dataset() {
193        let request = storage::files::ReadRequest {
194            store_id: "notes".into(),
195            path: "note.txt".into(),
196            max_bytes: Some(4096),
197        };
198        let value = serde_json::to_value(Arguments {
199            dataset: Dataset::Input,
200            kind: "files",
201            request: Operation {
202                operation: "read",
203                request: &request,
204            },
205        })
206        .unwrap();
207        assert_eq!(
208            value,
209            serde_json::json!({"dataset":"input","kind":"files","request":{"operation":"read","store_id":"notes","path":"note.txt","max_bytes":4096}})
210        );
211    }
212}