ahri_tre_protocol/
mutation.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4#[serde(try_from = "String", into = "String")]
5pub struct IdempotencyKey(String);
6
7impl IdempotencyKey {
8 pub fn new(value: impl Into<String>) -> Result<Self, String> {
9 let value = value.into();
10 let valid = !value.is_empty()
11 && value.len() <= 128
12 && value.bytes().all(|byte| {
13 byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'~' | b'-')
14 });
15 if valid {
16 Ok(Self(value))
17 } else {
18 Err("idempotency key must match [A-Za-z0-9._~-]{1,128}".to_string())
19 }
20 }
21
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25}
26
27impl TryFrom<String> for IdempotencyKey {
28 type Error = String;
29
30 fn try_from(value: String) -> Result<Self, Self::Error> {
31 Self::new(value)
32 }
33}
34
35impl From<IdempotencyKey> for String {
36 fn from(value: IdempotencyKey) -> Self {
37 value.0
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
42pub struct MutationOptions {
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub idempotency_key: Option<IdempotencyKey>,
45 #[serde(default, skip_serializing_if = "Vec::is_empty")]
46 pub preconditions: Vec<Precondition>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(tag = "kind", rename_all = "snake_case")]
51pub enum Precondition {
52 ResourceVersion { target: String, version: String },
53 Exists { target: String },
54 DoesNotExist { target: String },
55 SessionState { target: String, state: String },
56 GovernanceState { target: String, state: String },
57}
58
59impl MutationOptions {
60 pub fn is_empty(&self) -> bool {
61 self.idempotency_key.is_none() && self.preconditions.is_empty()
62 }
63}