cloud/envoy/
cloud_object.rs1use serde::{Deserialize, Serialize};
20
21use super::{InternalVerb, VerbCategory};
22
23pub struct CloudObjectBucketCreate;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
31pub struct CloudObjectBucketCreateInput {
32 pub name: String,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub location_hint: Option<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
46pub struct CloudObjectBucketCreateOutput {
47 pub endpoint: String,
51}
52
53impl InternalVerb for CloudObjectBucketCreate {
54 type Input = CloudObjectBucketCreateInput;
55 type Output = CloudObjectBucketCreateOutput;
56 const ID: &'static str = "cloud.object.bucket.create";
57 const CATEGORY: VerbCategory = VerbCategory::Cloud;
58}
59
60pub struct CloudObjectBucketDelete;
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
68pub struct CloudObjectBucketDeleteInput {
69 pub name: String,
74}
75
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
79#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
80pub struct CloudObjectBucketDeleteOutput {}
81
82impl InternalVerb for CloudObjectBucketDelete {
83 type Input = CloudObjectBucketDeleteInput;
84 type Output = CloudObjectBucketDeleteOutput;
85 const ID: &'static str = "cloud.object.bucket.delete";
86 const CATEGORY: VerbCategory = VerbCategory::Cloud;
87}
88
89pub struct CloudObjectBucketExists;
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
97pub struct CloudObjectBucketExistsInput {
98 pub name: String,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
105pub struct CloudObjectBucketExistsOutput {
106 pub exists: bool,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
109 pub endpoint: Option<String>,
110}
111
112impl InternalVerb for CloudObjectBucketExists {
113 type Input = CloudObjectBucketExistsInput;
114 type Output = CloudObjectBucketExistsOutput;
115 const ID: &'static str = "cloud.object.bucket.exists";
116 const CATEGORY: VerbCategory = VerbCategory::Cloud;
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn verb_ids_match_canonical_namespace() {
125 assert_eq!(CloudObjectBucketCreate::ID, "cloud.object.bucket.create");
126 assert_eq!(CloudObjectBucketDelete::ID, "cloud.object.bucket.delete");
127 assert_eq!(CloudObjectBucketExists::ID, "cloud.object.bucket.exists");
128 for id in [
129 CloudObjectBucketCreate::ID,
130 CloudObjectBucketDelete::ID,
131 CloudObjectBucketExists::ID,
132 ] {
133 assert!(id.starts_with("cloud."), "{id}");
134 }
135 }
136
137 #[test]
138 fn verbs_are_under_cloud_category() {
139 assert_eq!(CloudObjectBucketCreate::CATEGORY, VerbCategory::Cloud);
140 assert_eq!(CloudObjectBucketDelete::CATEGORY, VerbCategory::Cloud);
141 assert_eq!(CloudObjectBucketExists::CATEGORY, VerbCategory::Cloud);
142 }
143
144 #[test]
145 fn create_input_location_hint_is_optional() {
146 let with_hint = CloudObjectBucketCreateInput {
147 name: "my-bucket".into(),
148 location_hint: Some("WEUR".into()),
149 };
150 let wire = serde_json::to_string(&with_hint).unwrap();
151 let back: CloudObjectBucketCreateInput = serde_json::from_str(&wire).unwrap();
152 assert_eq!(back.location_hint.as_deref(), Some("WEUR"));
153
154 let no_hint = r#"{"name":"my-bucket"}"#;
155 let parsed: CloudObjectBucketCreateInput = serde_json::from_str(no_hint).unwrap();
156 assert!(parsed.location_hint.is_none());
157 }
158
159 #[test]
160 fn create_input_omits_hint_when_absent() {
161 let input = CloudObjectBucketCreateInput {
162 name: "b".into(),
163 location_hint: None,
164 };
165 let wire = serde_json::to_value(&input).unwrap();
166 assert!(!wire.as_object().unwrap().contains_key("location_hint"));
167 }
168
169 #[test]
170 fn delete_output_serializes_to_empty_object() {
171 let wire = serde_json::to_value(CloudObjectBucketDeleteOutput::default()).unwrap();
172 assert_eq!(wire, serde_json::json!({}));
173 }
174
175 #[test]
176 fn exists_output_omits_endpoint_when_absent() {
177 let out = CloudObjectBucketExistsOutput {
178 exists: false,
179 endpoint: None,
180 };
181 let wire = serde_json::to_value(&out).unwrap();
182 assert_eq!(wire, serde_json::json!({ "exists": false }));
183 }
184
185 #[test]
186 fn exists_output_includes_endpoint_when_present() {
187 let out = CloudObjectBucketExistsOutput {
188 exists: true,
189 endpoint: Some("https://acct.r2.cloudflarestorage.com".into()),
190 };
191 let wire = serde_json::to_value(&out).unwrap();
192 assert_eq!(wire["exists"], true);
193 assert!(wire["endpoint"].as_str().is_some());
194 }
195
196 #[cfg(feature = "json-schema")]
197 #[test]
198 fn verbs_emit_schemas_via_for_verb() {
199 use super::super::VerbDescriptor;
200
201 let create = VerbDescriptor::for_verb::<CloudObjectBucketCreate>();
202 assert_eq!(create.id, "cloud.object.bucket.create");
203 assert!(create.input_schema.to_string().contains("name"));
204
205 let delete = VerbDescriptor::for_verb::<CloudObjectBucketDelete>();
206 assert_eq!(delete.id, "cloud.object.bucket.delete");
207
208 let exists = VerbDescriptor::for_verb::<CloudObjectBucketExists>();
209 assert_eq!(exists.id, "cloud.object.bucket.exists");
210 assert!(exists.output_schema.to_string().contains("exists"));
211 }
212}