1#[derive(Clone, Debug, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize)]
21#[serde(transparent)]
22pub struct VaultIdStr(pub String);
23
24impl VaultIdStr {
25 pub fn new(value: impl Into<String>) -> Self {
27 Self(value.into())
28 }
29 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize)]
37#[serde(transparent)]
38pub struct PrincipalDidUri(pub String);
39
40impl PrincipalDidUri {
41 pub fn new(value: impl Into<String>) -> Self {
43 Self(value.into())
44 }
45 pub fn as_str(&self) -> &str {
47 &self.0
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize)]
54#[serde(transparent)]
55pub struct GrantId(pub String);
56
57impl GrantId {
58 pub fn new(value: impl Into<String>) -> Self {
60 Self(value.into())
61 }
62 pub fn as_str(&self) -> &str {
64 &self.0
65 }
66}
67
68#[derive(Clone, Debug, PartialEq, Eq, ::serde::Serialize, ::serde::Deserialize)]
70#[serde(tag = "kind", rename_all = "PascalCase")]
71pub enum VaultMutationKind {
72 WriteInto {
74 dest_vault_id: VaultIdStr,
76 },
77 ShareFrom {
79 src_vault_id: VaultIdStr,
81 },
82 CrossVaultPromote {
84 artifact_id: String,
86 },
87 Redelegate {
89 downstream_grantee_id: PrincipalDidUri,
91 },
92 Revoke,
94 BridgeStageTransition {
96 from_stage: String,
98 to_stage: String,
100 },
101 Custom {
103 snapp_id: String,
105 mutation_slug: String,
107 },
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, ::serde::Serialize, ::serde::Deserialize)]
113#[serde(deny_unknown_fields)]
114pub struct VaultMutationMandate {
115 pub kind: VaultMutationKind,
117 pub grant_scope_id: GrantId,
119 #[serde(skip_serializing_if = "Option::is_none", default)]
123 pub ap2_signed_payload_b64: Option<String>,
124}
125
126#[cfg(test)]
127mod tests {
128 #[test]
136 fn write_into_wire_shape() {
137 let v = super::VaultMutationKind::WriteInto {
142 dest_vault_id: super::VaultIdStr::new("11111111-1111-4111-8111-111111111111"),
143 };
144 let json = serde_json::to_value(&v).unwrap();
145 std::assert_eq!(
146 json,
147 serde_json::json!({
148 "kind": "WriteInto",
149 "dest_vault_id": "11111111-1111-4111-8111-111111111111"
150 })
151 );
152 }
153
154 #[test]
155 fn share_from_wire_shape() {
156 let v = super::VaultMutationKind::ShareFrom {
160 src_vault_id: super::VaultIdStr::new("22222222-2222-4222-8222-222222222222"),
161 };
162 let json = serde_json::to_value(&v).unwrap();
163 std::assert_eq!(
164 json,
165 serde_json::json!({
166 "kind": "ShareFrom",
167 "src_vault_id": "22222222-2222-4222-8222-222222222222"
168 })
169 );
170 }
171
172 #[test]
173 fn cross_vault_promote_wire_shape() {
174 let v = super::VaultMutationKind::CrossVaultPromote {
178 artifact_id: String::from("artifact-1"),
179 };
180 let json = serde_json::to_value(&v).unwrap();
181 std::assert_eq!(
182 json,
183 serde_json::json!({"kind": "CrossVaultPromote", "artifact_id": "artifact-1"})
184 );
185 }
186
187 #[test]
188 fn redelegate_wire_shape() {
189 let v = super::VaultMutationKind::Redelegate {
193 downstream_grantee_id: super::PrincipalDidUri::new("did:key:zDownstream"),
194 };
195 let json = serde_json::to_value(&v).unwrap();
196 std::assert_eq!(
197 json,
198 serde_json::json!({
199 "kind": "Redelegate",
200 "downstream_grantee_id": "did:key:zDownstream"
201 })
202 );
203 }
204
205 #[test]
206 fn revoke_wire_shape() {
207 let v = super::VaultMutationKind::Revoke;
211 let json = serde_json::to_value(&v).unwrap();
212 std::assert_eq!(json, serde_json::json!({"kind": "Revoke"}));
213 }
214
215 #[test]
216 fn bridge_stage_transition_wire_shape() {
217 let v = super::VaultMutationKind::BridgeStageTransition {
221 from_stage: String::from("staged"),
222 to_stage: String::from("committed"),
223 };
224 let json = serde_json::to_value(&v).unwrap();
225 std::assert_eq!(
226 json,
227 serde_json::json!({
228 "kind": "BridgeStageTransition",
229 "from_stage": "staged",
230 "to_stage": "committed"
231 })
232 );
233 }
234
235 #[test]
236 fn custom_wire_shape() {
237 let v = super::VaultMutationKind::Custom {
241 snapp_id: String::from("snapp-1"),
242 mutation_slug: String::from("do-thing"),
243 };
244 let json = serde_json::to_value(&v).unwrap();
245 std::assert_eq!(
246 json,
247 serde_json::json!({
248 "kind": "Custom",
249 "snapp_id": "snapp-1",
250 "mutation_slug": "do-thing"
251 })
252 );
253 }
254
255 #[test]
258 fn all_kinds_round_trip() {
259 let kinds = std::vec![
263 super::VaultMutationKind::WriteInto {
264 dest_vault_id: super::VaultIdStr::new("11111111-1111-4111-8111-111111111111"),
265 },
266 super::VaultMutationKind::ShareFrom {
267 src_vault_id: super::VaultIdStr::new("22222222-2222-4222-8222-222222222222"),
268 },
269 super::VaultMutationKind::CrossVaultPromote {
270 artifact_id: String::from("artifact-1"),
271 },
272 super::VaultMutationKind::Redelegate {
273 downstream_grantee_id: super::PrincipalDidUri::new("did:key:zDownstream"),
274 },
275 super::VaultMutationKind::Revoke,
276 super::VaultMutationKind::BridgeStageTransition {
277 from_stage: String::from("staged"),
278 to_stage: String::from("committed"),
279 },
280 super::VaultMutationKind::Custom {
281 snapp_id: String::from("snapp-1"),
282 mutation_slug: String::from("do-thing"),
283 },
284 ];
285 for kind in kinds {
286 let s = serde_json::to_string(&kind).unwrap();
287 let back: super::VaultMutationKind = serde_json::from_str(&s).unwrap();
288 std::assert_eq!(kind, back, "round-trip mismatch for {:?}", kind);
289 }
290 }
291
292 #[test]
295 fn mandate_wire_shape_without_payload() {
296 let m = super::VaultMutationMandate {
301 kind: super::VaultMutationKind::WriteInto {
302 dest_vault_id: super::VaultIdStr::new("11111111-1111-4111-8111-111111111111"),
303 },
304 grant_scope_id: super::GrantId::new("33333333-3333-4333-8333-333333333333"),
305 ap2_signed_payload_b64: std::option::Option::None,
306 };
307 let json = serde_json::to_value(&m).unwrap();
308 std::assert_eq!(
311 json,
312 serde_json::json!({
313 "kind": {
314 "kind": "WriteInto",
315 "dest_vault_id": "11111111-1111-4111-8111-111111111111"
316 },
317 "grant_scope_id": "33333333-3333-4333-8333-333333333333"
318 })
319 );
320 }
321
322 #[test]
323 fn mandate_wire_shape_with_payload() {
324 let m = super::VaultMutationMandate {
329 kind: super::VaultMutationKind::Revoke,
330 grant_scope_id: super::GrantId::new("33333333-3333-4333-8333-333333333333"),
331 ap2_signed_payload_b64: std::option::Option::Some(String::from("cGF5bG9hZA==")),
332 };
333 let json = serde_json::to_value(&m).unwrap();
334 std::assert_eq!(
335 json,
336 serde_json::json!({
337 "kind": {"kind": "Revoke"},
338 "grant_scope_id": "33333333-3333-4333-8333-333333333333",
339 "ap2_signed_payload_b64": "cGF5bG9hZA=="
340 })
341 );
342 }
343
344 #[test]
345 fn mandate_round_trip() {
346 let m = super::VaultMutationMandate {
350 kind: super::VaultMutationKind::Custom {
351 snapp_id: String::from("snapp-1"),
352 mutation_slug: String::from("do-thing"),
353 },
354 grant_scope_id: super::GrantId::new("33333333-3333-4333-8333-333333333333"),
355 ap2_signed_payload_b64: std::option::Option::Some(String::from("cGF5bG9hZA==")),
356 };
357 let s = serde_json::to_string(&m).unwrap();
358 let back: super::VaultMutationMandate = serde_json::from_str(&s).unwrap();
359 std::assert_eq!(m, back);
360 }
361
362 #[test]
363 fn mandate_deserializes_without_ap2_payload() {
364 let s = serde_json::json!({
368 "kind": {"kind": "Revoke"},
369 "grant_scope_id": "33333333-3333-4333-8333-333333333333"
370 })
371 .to_string();
372 let m: super::VaultMutationMandate =
373 serde_json::from_str(&s).expect("must deserialize with ap2_signed_payload_b64 omitted");
374 std::assert!(m.ap2_signed_payload_b64.is_none());
375 }
376
377 #[test]
380 fn id_newtypes_are_transparent_strings() {
381 std::assert_eq!(
386 serde_json::to_value(super::VaultIdStr::new("v-1")).unwrap(),
387 serde_json::json!("v-1")
388 );
389 std::assert_eq!(
390 serde_json::to_value(super::PrincipalDidUri::new("did:key:zX")).unwrap(),
391 serde_json::json!("did:key:zX")
392 );
393 std::assert_eq!(
394 serde_json::to_value(super::GrantId::new("g-1")).unwrap(),
395 serde_json::json!("g-1")
396 );
397 std::assert_eq!(super::VaultIdStr::new("v-1").as_str(), "v-1");
398 std::assert_eq!(super::PrincipalDidUri::new("did:key:zX").as_str(), "did:key:zX");
399 std::assert_eq!(super::GrantId::new("g-1").as_str(), "g-1");
400 }
401}