1use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub enum Patch {
9 Merge(serde_json::Value),
13
14 Strategic(serde_json::Value),
19
20 Json(Vec<JsonPatchOp>),
23
24 Apply {
29 field_manager: String,
31 force: bool,
33 body: serde_json::Value,
35 },
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "lowercase", tag = "op")]
41pub enum JsonPatchOp {
42 Add { path: String, value: serde_json::Value },
44 Remove { path: String },
46 Replace { path: String, value: serde_json::Value },
48 Copy { from: String, path: String },
50 Move { from: String, path: String },
52 Test { path: String, value: serde_json::Value },
54}
55
56impl Patch {
57 #[must_use]
60 pub fn content_type(&self) -> &'static str {
61 match self {
62 Self::Merge(_) => "application/merge-patch+json",
63 Self::Strategic(_) => "application/strategic-merge-patch+json",
64 Self::Json(_) => "application/json-patch+json",
65 Self::Apply { .. } => "application/apply-patch+yaml",
66 }
67 }
68
69 pub fn body_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
76 match self {
77 Self::Merge(v) | Self::Strategic(v) => serde_json::to_vec(v),
78 Self::Json(ops) => serde_json::to_vec(ops),
79 Self::Apply { body, .. } => serde_json::to_vec(body),
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use serde_json::json;
88
89 #[test]
90 fn merge_content_type() {
91 let p = Patch::Merge(json!({"spec": {"replicas": 3}}));
92 assert_eq!(p.content_type(), "application/merge-patch+json");
93 }
94
95 #[test]
96 fn strategic_content_type() {
97 let p = Patch::Strategic(json!({}));
98 assert_eq!(p.content_type(), "application/strategic-merge-patch+json");
99 }
100
101 #[test]
102 fn json_patch_content_type() {
103 let p = Patch::Json(vec![JsonPatchOp::Add {
104 path: "/spec/replicas".into(),
105 value: json!(3),
106 }]);
107 assert_eq!(p.content_type(), "application/json-patch+json");
108 }
109
110 #[test]
111 fn apply_content_type_and_field_manager() {
112 let p = Patch::Apply {
113 field_manager: "engenho-scheduler".into(),
114 force: false,
115 body: json!({"apiVersion": "v1", "kind": "Pod"}),
116 };
117 assert_eq!(p.content_type(), "application/apply-patch+yaml");
118 }
119
120 #[test]
121 fn json_patch_body_roundtrips() {
122 let p = Patch::Json(vec![
123 JsonPatchOp::Add { path: "/a".into(), value: json!(1) },
124 JsonPatchOp::Remove { path: "/b".into() },
125 JsonPatchOp::Replace { path: "/c".into(), value: json!("x") },
126 JsonPatchOp::Copy { from: "/d".into(), path: "/e".into() },
127 JsonPatchOp::Move { from: "/f".into(), path: "/g".into() },
128 JsonPatchOp::Test { path: "/h".into(), value: json!(true) },
129 ]);
130 let bytes = p.body_bytes().unwrap();
131 let back: Vec<JsonPatchOp> = serde_json::from_slice(&bytes).unwrap();
133 assert_eq!(back.len(), 6);
134 }
135}