microsandbox_types/cloud/
compat.rs1use serde::Deserialize;
8use serde::de::DeserializeOwned;
9
10use super::{
11 CloudCreateSandboxRequest, CloudPullPolicy, CloudRootfsSource, CloudSandboxComputeResources,
12 CloudSandboxResources, CloudSandboxSpec,
13};
14
15type CompatResult<T> = Result<T, String>;
16
17impl<'de> Deserialize<'de> for CloudCreateSandboxRequest {
18 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
19 where
20 D: serde::Deserializer<'de>,
21 {
22 let value = serde_json::Value::deserialize(deserializer)?;
23 deserialize_request(value).map_err(serde::de::Error::custom)
24 }
25}
26
27fn deserialize_request(value: serde_json::Value) -> CompatResult<CloudCreateSandboxRequest> {
28 let object = value
29 .as_object()
30 .ok_or_else(|| "cloud sandbox create request must be an object".to_owned())?;
31 let has_image = object.contains_key("image");
32 let has_disk_snapshot = object.contains_key("disk_snapshot_ref");
33
34 if let Some(source) = object.get("source") {
35 let source = source
36 .as_str()
37 .ok_or_else(|| "cloud sandbox create source must be a string".to_owned())?
38 .to_owned();
39 let mut value = value;
40 request_object_mut(&mut value)?.remove("source");
41 return deserialize_tagged_request(value, &source);
42 }
43
44 match (has_image, has_disk_snapshot) {
45 (true, false) => deserialize_legacy_image_request(value),
46 (false, true) => deserialize_tagged_request(value, "disk_snapshot"),
47 (true, true) => Err("image and disk_snapshot_ref are mutually exclusive".to_owned()),
48 (false, false) => Err("exactly one of image and disk_snapshot_ref is required".to_owned()),
49 }
50}
51
52fn deserialize_tagged_request(
53 value: serde_json::Value,
54 source: &str,
55) -> CompatResult<CloudCreateSandboxRequest> {
56 match source {
57 "oci" => deserialize_oci_request(value),
58 "bind" => deserialize_bind_request(value),
59 "disk_image" => deserialize_disk_image_request(value),
60 "disk_snapshot" => deserialize_disk_snapshot_request(value),
61 unknown => Err(format!(
62 "unknown variant `{unknown}`, expected one of `oci`, `bind`, `disk_image`, `disk_snapshot`"
63 )),
64 }
65}
66
67fn deserialize_oci_request(
68 mut value: serde_json::Value,
69) -> CompatResult<CloudCreateSandboxRequest> {
70 let object = request_object_mut(&mut value)?;
71 let reference = take_required_field(object, "reference")?;
72 let resources = take_default_field(object, "resources")?;
73 let patches = take_default_field(object, "patches")?;
74 let pull_policy = take_default_field(object, "pull_policy")?;
75 let sandbox = deserialize_common_spec(value)?;
76
77 Ok(CloudCreateSandboxRequest::Oci {
78 sandbox,
79 reference,
80 resources,
81 patches,
82 pull_policy,
83 })
84}
85
86fn deserialize_bind_request(
87 mut value: serde_json::Value,
88) -> CompatResult<CloudCreateSandboxRequest> {
89 let object = request_object_mut(&mut value)?;
90 let path = take_required_field(object, "path")?;
91 let resources = take_compute_resources(object)?;
92 let patches = take_default_field(object, "patches")?;
93 let sandbox = deserialize_common_spec(value)?;
94
95 Ok(CloudCreateSandboxRequest::Bind {
96 sandbox,
97 path,
98 resources,
99 patches,
100 })
101}
102
103fn deserialize_disk_image_request(
104 mut value: serde_json::Value,
105) -> CompatResult<CloudCreateSandboxRequest> {
106 let object = request_object_mut(&mut value)?;
107 let path = take_required_field(object, "path")?;
108 let format = take_required_field(object, "format")?;
109 let fstype = take_default_field(object, "fstype")?;
110 let resources = take_compute_resources(object)?;
111 let patches = take_default_field(object, "patches")?;
112 let sandbox = deserialize_common_spec(value)?;
113
114 Ok(CloudCreateSandboxRequest::DiskImage {
115 sandbox,
116 path,
117 format,
118 fstype,
119 resources,
120 patches,
121 })
122}
123
124fn deserialize_disk_snapshot_request(
125 mut value: serde_json::Value,
126) -> CompatResult<CloudCreateSandboxRequest> {
127 let object = request_object_mut(&mut value)?;
128 let disk_snapshot_ref = take_required_field(object, "disk_snapshot_ref")?;
129 let resources = take_compute_resources(object)?;
130 let pull_policy = take_default_field(object, "pull_policy")?;
131 let sandbox = deserialize_common_spec(value)?;
132
133 Ok(CloudCreateSandboxRequest::DiskSnapshot {
134 sandbox,
135 disk_snapshot_ref,
136 resources,
137 pull_policy,
138 })
139}
140
141fn deserialize_legacy_image_request(
142 mut value: serde_json::Value,
143) -> CompatResult<CloudCreateSandboxRequest> {
144 let object = request_object_mut(&mut value)?;
145 let image: CloudRootfsSource = take_required_field(object, "image")?;
146 let resources: CloudSandboxResources = take_default_field(object, "resources")?;
147 let patches = take_default_field(object, "patches")?;
148 let pull_policy: CloudPullPolicy = take_default_field(object, "pull_policy")?;
149 let sandbox = deserialize_common_spec(value)?;
150
151 match image {
152 CloudRootfsSource::Oci { reference } => Ok(CloudCreateSandboxRequest::Oci {
153 sandbox,
154 reference,
155 resources,
156 patches,
157 pull_policy,
158 }),
159 CloudRootfsSource::Bind { path } => {
160 reject_non_oci_legacy_options(&resources, pull_policy)?;
161 Ok(CloudCreateSandboxRequest::Bind {
162 sandbox,
163 path,
164 resources: resources.into(),
165 patches,
166 })
167 }
168 CloudRootfsSource::DiskImage {
169 path,
170 format,
171 fstype,
172 } => {
173 reject_non_oci_legacy_options(&resources, pull_policy)?;
174 Ok(CloudCreateSandboxRequest::DiskImage {
175 sandbox,
176 path,
177 format,
178 fstype,
179 resources: resources.into(),
180 patches,
181 })
182 }
183 }
184}
185
186fn reject_non_oci_legacy_options(
187 resources: &CloudSandboxResources,
188 pull_policy: CloudPullPolicy,
189) -> CompatResult<()> {
190 if resources.disk_size_mib.is_some() {
191 return Err("resources.disk_size_mib is only valid for OCI source".to_owned());
192 }
193 if pull_policy != CloudPullPolicy::default() {
194 return Err("pull_policy is only valid for OCI and disk_snapshot sources".to_owned());
195 }
196 Ok(())
197}
198
199fn request_object_mut(
200 value: &mut serde_json::Value,
201) -> CompatResult<&mut serde_json::Map<String, serde_json::Value>> {
202 value
203 .as_object_mut()
204 .ok_or_else(|| "cloud sandbox create request must be an object".to_owned())
205}
206
207fn take_required_field<T: DeserializeOwned>(
208 object: &mut serde_json::Map<String, serde_json::Value>,
209 name: &'static str,
210) -> CompatResult<T> {
211 let value = object
212 .remove(name)
213 .ok_or_else(|| format!("missing field `{name}`"))?;
214 serde_json::from_value(value).map_err(|error| error.to_string())
215}
216
217fn take_default_field<T: Default + DeserializeOwned>(
218 object: &mut serde_json::Map<String, serde_json::Value>,
219 name: &'static str,
220) -> CompatResult<T> {
221 object
222 .remove(name)
223 .map(serde_json::from_value)
224 .transpose()
225 .map_err(|error| error.to_string())
226 .map(Option::unwrap_or_default)
227}
228
229fn take_compute_resources(
230 object: &mut serde_json::Map<String, serde_json::Value>,
231) -> CompatResult<CloudSandboxComputeResources> {
232 let resources: CloudSandboxResources = take_default_field(object, "resources")?;
233 if resources.disk_size_mib.is_some() {
234 return Err("resources.disk_size_mib is only valid for OCI source".to_owned());
235 }
236 Ok(resources.into())
237}
238
239fn deserialize_common_spec(value: serde_json::Value) -> CompatResult<CloudSandboxSpec> {
240 serde_json::from_value(value).map_err(|error| error.to_string())
241}